装饰器 · 机制概念

装饰器是以函数或类为参数、并返回替换函数或类的可调用对象。可在不修改原定义的情况下附加行为。

修饰函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def warp(obj):
return obj

# 写法 1:@ 语法
@warp # 等价于 foo = warp(foo)
def foo():
print("hello decorator!")

# 写法 2:显式包装
def bar():
print("hello decorator!")
bar = warp(bar)

foo() # => hello decorator!

@warpfoo = warp(foo) 完全等价:装饰器接收原函数,返回一个新可调用对象并绑定到原名字。

修饰类

1
2
3
4
5
6
7
8
9
def warp(obj):
obj.name = "python"
return obj

@warp # 等价于 Bar = warp(Bar)
class Bar:
pass

print(Bar.name) # => python

自定义装饰器示例:function_timer

标准库装饰器(@property@lru_cache 等)底层也是「接收 callable、返回 callable」。下面用计时装饰器走通完整写法,帮助理解闭包、参数转发与元数据保留。

无参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import wraps
import time

def function_timer(fn):
@wraps(fn) # 保留 fn 的 __name__、__doc__ 等,避免被 wrapper 覆盖
def measure_time(*args, **kwargs):
t1 = time.perf_counter()
result = fn(*args, **kwargs) # 调用原函数
t2 = time.perf_counter()
print(f"@timefn: {fn.__name__} took {t2 - t1:.5f} s")
return result
return measure_time

@function_timer
def work(n):
"""模拟耗时任务"""
return sum(range(n))

print(work(1_000_000))
1
2
@timefn: work took 0.0xxxx s
499999500000

展开等价关系:

1
2
work = function_timer(work)
# work 实际是 measure_time;调用 work() 时先计时,再执行原 work 逻辑

要点

  • 外层 function_timer(fn) 接收被装饰函数;内层 measure_time 是实际替换上去的 wrapper
  • *args, **kwargs 保证任意签名都能转发
  • @wraps(fn) 必不可少,否则 help(work)mock.patch 路径会指向 wrapper

带参装饰器(三层嵌套)

装饰器本身需要配置项时,多一层工厂函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def function_timer(threshold=1.0):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
t1 = time.perf_counter()
result = fn(*args, **kwargs)
elapsed = time.perf_counter() - t1
if elapsed > threshold:
print(f"SLOW: {fn.__name__} {elapsed:.3f}s")
return result
return wrapper
return decorator

@function_timer(threshold=0.01)
def fast():
return 1

等价于 fast = function_timer(threshold=0.01)(fast):先调用工厂得到 decorator,再装饰目标函数。

写自定义装饰器时注意

  • 计 interval 用 time.perf_counter(),不要用易受系统时钟调整影响的 time.time()
  • 装饰异步函数时 wrapper 须为 async def,内部 await fn(...)
  • 生产环境 profiling 用 cProfile / timeit,计时打印适合开发调试

与专篇的关系

本目录各专篇默认读者已理解上述等价关系与自定义写法;具体标准库/第三方装饰器的参数与示例见 00.索引-装饰器速查 与各 {作用对象}-{使用场景}-{名称}.md 专篇。

-------------本文结束感谢您的阅读-------------