当前位置:首页 > Python > 正文

Python装饰器保留函数元信息完整教程 - functools.wraps详解

Python装饰器如何保留原函数信息

为什么需要保留函数信息?

Python装饰器在修改函数行为时,会覆盖函数的元信息(如函数名、文档字符串等)。这会导致:

  • 1. 函数名(__name__)变成装饰器内部函数名
  • 2. 文档字符串(__doc__)丢失
  • 3. 影响调试和日志输出
  • 4. 破坏自省工具功能

解决方案:使用functools.wraps

Python标准库中的functools.wraps装饰器可以解决这个问题:

from functools import wraps

def my_decorator(func):
    @wraps(func)  # 关键步骤:保留原函数信息
    def wrapper(*args, **kwargs):
        """装饰器内部包装函数"""
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

对比示例:使用wraps前后差异

未保留元信息的装饰器

def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def example():
    """示例函数文档字符串"""
    pass

print(example.__name__)  # 输出:wrapper
print(example.__doc__)   # 输出:None

使用wraps保留元信息

from functools import wraps

def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def example():
    """示例函数文档字符串"""
    pass

print(example.__name__)  # 输出:example
print(example.__doc__)   # 输出:示例函数文档字符串

保留元信息的工作原理

functools.wraps实际上执行了以下操作:

def wraps(original_func):
    def decorator(wrapper_func):
        # 复制关键元数据
        wrapper_func.__name__ = original_func.__name__
        wrapper_func.__doc__ = original_func.__doc__
        wrapper_func.__module__ = original_func.__module__
        wrapper_func.__annotations__ = original_func.__annotations__
        return wrapper_func
    return decorator

实际上它会复制更多属性,包括:__qualname__, __module__, __annotations__等

高级用法:自定义元信息

在保留原信息基础上添加新元数据:

from functools import wraps

def versioned(version):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        # 添加自定义元数据
        wrapper.__version__ = version
        return wrapper
    return decorator

@versioned("1.2.0")
def api_handler():
    """处理API请求"""
    pass

print(api_handler.__name__)    # 输出:api_handler
print(api_handler.__version__) # 输出:1.2.0

实际应用场景

  • Web框架路由:Flask/Django保留视图函数信息
  • API版本控制:添加版本元数据同时保留文档
  • 调试工具:在日志中显示原始函数名
  • 文档生成:Sphinx等工具依赖函数元信息

最佳实践总结

  1. 始终在装饰器中使用@wraps(func)
  2. 需要添加新元数据时,在@wraps之后设置
  3. 使用inspect模块验证元信息完整性
  4. 在类装饰器中同样适用:@wraps(cls)

发表评论