在Python中,高阶函数装饰器是一个接受另一个函数(或类)并返回一个新函数(或新修改类)的函数。装饰器通常用于实现“包装”模式,可以在不改变现有函数源代码的情况下,为现有函数添加额外的逻辑(例如,日志记录、缓存、权限检查等)。
为了保留原始函数的名称、文档和其他元数据,建议使用functools.wraps函数:
import functools def log_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'Calling {func.__name__}') return func(*args, **kwargs) return wrapper @log_decorator def add(a, b): """Add two numbers""" return a + b print(add(2, 3)) # Output: Calling add 5 print(add.__name__) # Output: add print(add.__doc__) # Output: Add two numbers
关键点:如果没有functools.wraps,包装函数将失去原始的名称、文档和其他元数据, 这会对调试和自动文档生成产生负面影响。
如果装饰一个函数而不使用functools.wraps,函数的__name__和__doc__属性会发生什么?
答案: 它们将从内部包装函数(通常是“wrapper”)继承,您将失去原始的元数据。
def simple_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @simple_decorator def f(): """This is docstring""" pass print(f.__name__) # Output: 'wrapper' (不是'f') print(f.__doc__) # Output: None (而不是'This is docstring')
故事
在一个项目中实现了复杂的装饰器系统用于API端点的日志记载,但没有应用functools.wraps。结果,自动生成的文档(Swagger/OpenAPI)和反射工具显示所有端点的名称为'wrapper',而文档消失了。这大大增加了维护、测试和支持的难度。
故事
在使用pytest编写单元测试时,自动发现测试失败:使用装饰器(没有wraps)装饰的测试函数未被发现,因为它们的__name__不正确。原因是pytest根据名称查找函数。
故事
在跟踪异常堆栈时,堆栈跟踪总是指向“wrapper”,无法辨别究竟是哪个函数引发了错误,因为根元数据由于缺少用户自定义装饰器中的functools.wraps而丢失。