Python 装饰器是一种强大的语法特性,用于在不修改原函数代码的前提下,动态地给函数或类添加额外功能。它在日志记录、性能测试、权限验证等场景中非常常用。
装饰器的基础:什么是装饰器?
装饰器本质上是一个函数,它接收一个函数作为参数,然后返回一个新的函数(或其他可调用对象)。其核心作用是:在不改变原函数代码和调用方式的前提下,为函数增加额外功能。
举个生活例子:给手机贴保护膜,手机(原函数)的核心功能(打电话、上网网)没变,但新增了 “防刮” 功能(装饰器添加的功能)。
入门:最简单的装饰器
假设我们需要给函数添加 “执行前打印日志” 的功能,先用常规方式实现,再用装饰器简化。
1. 不用装饰器的写法
def add(a, b):
return a + b
# 需求:调用add前打印"开始计算..."
print("开始计算...")
result = add(1, 2)
print(result)
这种方式的问题:如果有 100 个函数需要加日志,就要写 100 次打印代码,重复且难维护。
2. 用装饰器简化
装饰器可以将 “打印日志” 这个通用功能抽离出来,复用给任何函数:
# 定义装饰器(本质是一个函数)
def log_decorator(func):
# 定义一个新函数,包裹原函数并添加额外功能
def wrapper(*args, **kwargs):
print("开始计算...") # 额外功能
result = func(*args, **kwargs) # 调用原函数
return result
return wrapper # 返回新函数
# 使用装饰器(语法糖:@装饰器名)
@log_decorator
def add(a, b):
return a + b
# 调用函数(和原来的调用方式完全一样)
print(add(1, 2)) # 输出:开始计算... 3
核心逻辑:@log_decorator
等价于 add = log_decorator(add)
,即把 add
函数传给装饰器,再用装饰器返回的 wrapper
函数替换原来的 add
。
装饰器的语法糖
@装饰器名
是 Python 提供的语法糖,用于简化装饰器的使用。它的执行时机是函数定义时(不是调用时)。
@log_decorator
def multiply(a, b):
return a * b
# 等价于:
# multiply = log_decorator(multiply)
进阶:带参数的装饰器
有时装饰器需要自定义参数(比如日志的前缀),这时需要在装饰器外层再包一层函数,用于接收参数。
示例:给日志添加自定义前缀
# 外层函数:接收装饰器参数
def log_decorator_with_prefix(prefix):
# 中层函数:接收被装饰的函数(真正的装饰器)
def decorator(func):
# 内层函数:包裹原函数
def wrapper(*args, **kwargs):
print(f"[{prefix}] 开始计算...") # 使用装饰器参数
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 使用带参数的装饰器
@log_decorator_with_prefix("INFO")
def add(a, b):
return a + b
print(add(1, 2)) # 输出:[INFO] 开始计算... 3
执行逻辑:@log_decorator_with_prefix("INFO")
等价于:add = log_decorator_with_prefix("INFO")(add)
即先调用外层函数得到真正的装饰器 decorator
,再用它装饰 add
。
多个装饰器的执行顺序
一个函数可以被多个装饰器装饰,执行顺序是从下往上(类似洋葱剥壳)。
示例:
def decorator1(func):
def wrapper(*args, **kwargs):
print("装饰器1:开始")
result = func(*args, **kwargs)
print("装饰器1:结束")
return result
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print("装饰器2:开始")
result = func(*args, **kwargs)
print("装饰器2:结束")
return result
return wrapper
# 多个装饰器:从下往上执行
@decorator1
@decorator2
def say_hello():
print("Hello!")
say_hello()
输出结果:
装饰器1:开始
装饰器2:开始
Hello!
装饰器2:结束
装饰器1:结束
原理:等价于 say_hello = decorator1(decorator2(say_hello))
,所以先执行 decorator2
的外层逻辑,再执行 decorator1
的外层逻辑。
原理剖析:装饰器为什么能工作?
装饰器的实现依赖于 Python 的两个核心特性:函数是一等公民 和 闭包。
1. 函数是一等公民
在 Python 中,函数可以:
- 作为参数传递给其他函数
- 作为返回值从其他函数返回
- 赋值给变量
这是装饰器能接收函数、返回函数的基础。例如:
def add(a, b):
return a + b
# 函数作为变量
func_var = add
print(func_var(1, 2)) # 3
# 函数作为参数
def call_func(func, a, b):
return func(a, b)
print(call_func(add, 1, 2)) # 3
2. 闭包(Closure)
装饰器中的 wrapper
函数就是一个闭包。闭包指的是:内部函数可以访问外部函数中定义的变量或参数,即使外部函数已经执行完毕。
在之前的 log_decorator
中:
def log_decorator(func): # 外部函数
def wrapper(*args, **kwargs): # 内部函数(闭包)
print("开始计算...")
result = func(*args, **kwargs) # 访问外部函数的参数 func
return result
return wrapper
wrapper
函数在 log_decorator
执行完后依然能访问 func
(原函数),这就是闭包的作用。
3. 被装饰函数的元信息丢失问题
装饰器会用 wrapper
替换原函数,导致原函数的 __name__
(函数名)、__doc__
(文档字符串)等元信息丢失:
@log_decorator
def add(a, b):
"""返回两个数的和"""
return a + b
print(add.__name__) # 输出:wrapper(而不是 add)
print(add.__doc__) # 输出:None(而不是 "返回两个数的和")
解决方法:使用 functools.wraps
保留原函数元信息:
import functools
def log_decorator(func):
@functools.wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
print("开始计算...")
result = func(*args, **kwargs)
return result
return wrapper
@log_decorator
def add(a, b):
"""返回两个数的和"""
return a + b
print(add.__name__) # 输出:add
print(add.__doc__) # 输出:返回两个数的和
装饰器的应用场景
1.** 日志记录 :自动记录函数调用时间、参数、返回值
2. 性能测试 :统计函数执行耗时
3. 权限验证 :调用函数前检查用户是否有权限
4. 缓存 **:缓存函数的计算结果,避免重复计算
示例(统计函数执行时间):
import time
import functools
def timer_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行耗时:{end_time - start_time:.4f}秒")
return result
return wrapper
@timer_decorator
def slow_function():
time.sleep(1) # 模拟耗时操作
slow_function() # 输出:slow_function 执行耗时:1.0012秒
MCP Server 中的装饰器
在 MCP Server 中,装饰器是 连接 “业务逻辑” 与 “MCP 协议” 的核心桥梁。其核心作用是:
- 用
@mcp.tool()
快速定义符合 MCP 规范的外部工具(此外还有 resource 和 prompt); - 自动处理协议适配、参数校验、异常标准化等底层工作。
@mcp.tool()
本质上是 FastMCP
类的一个方法,其核心逻辑是:
- 接收被装饰的函数,解析其参数、返回值、文档字符串等元数据。
- 将这些元数据转换为 MCP 协议要求的格式(如
inputSchema
为 JSON Schema)。 - 通过
ToolManager
将工具注册到服务器中,使其可被客户端发现和调用(通过list_tools
接口)。
相关代码可参考 ToolManager
的 add_tool
方法和 Tool
类的 from_function
方法,它们负责工具的元数据解析和注册。

# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
# Add a prompt
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""Generate a greeting prompt"""
styles = {
"friendly": "Please write a warm, friendly greeting",
"formal": "Please write a formal, professional greeting",
"casual": "Please write a casual, relaxed greeting",
}
return f"{styles.get(style, styles['friendly'])} for someone named {name}."
总结
- 装饰器是 “包装函数”,用于给原函数添加额外功能,不修改原代码。
- 核心语法:
@装饰器名
等价于函数 = 装饰器(函数)
。 - 原理依赖:函数是一等公民 + 闭包。
- 进阶用法:带参数的装饰器、多个装饰器组合、
functools.wraps
保留元信息。
装饰器是 Python 中 “优雅编程” 的典型体现,掌握它能极大提升代码的复用性和可读性。