装饰器作为语法糖在Python 2.4版本中出现,以简化与高阶函数的工作——那些接受或返回其他函数的函数。扩展函数功能的方法的演变导致了通过@decorator进行简洁且生动的注释格式。
在大型项目中,通常需要修改或包装某些函数以添加额外的逻辑:日志记录、访问检查、缓存、运行时间测量。没有装饰器时,必须手动调用包装函数,这会使代码冗长。
装饰器允许将重复的方面抽出到单独的包装中,从而提高代码的可读性和可重用性。使用@decorator可以优雅地将功能添加到函数和方法中:
示例代码:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # 将输出执行所花费的时间
关键特性:
装饰器可以改变被包装函数的签名吗?
人们常常错误地认为装饰器不会改变函数的签名。实际上,如果不使用模块functools.wraps,元信息会消失,这可能导致自动文档生成或反射中的意外错误。
示例代码:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
装饰器可以参数化吗?
人们常常回答说不可以。实际上,通过额外的嵌套层——返回装饰器的函数,可以创建参数化装饰器。
示例代码:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")
可以对一个函数应用多个装饰器吗?执行顺序是什么?
人们常常错误地认为顺序不重要或者与代码中装饰器的排列顺序相同。实际上,最底层的装饰器首先应用,然后是下一个,依此类推。
示例代码:
def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core
手动将运行时间的日志记录添加到10个函数中,通过复制和编码。
优点:
缺点:
所有计时逻辑被提取到装饰器中,所有需要此度量的函数都被 @timing_decorator 装饰。
优点:
缺点:
functools.wraps的信息;