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 类的一个方法,其核心逻辑是:

  1. 接收被装饰的函数,解析其参数、返回值、文档字符串等元数据。
  2. 将这些元数据转换为 MCP 协议要求的格式(如 inputSchema 为 JSON Schema)。
  3. 通过 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 中 “优雅编程” 的典型体现,掌握它能极大提升代码的复用性和可读性。