- 装饰器是什么?
一句话:装饰器就是接收一个可调用对象并返回一个可调用对象的函数,用来在不改动原函数源码的前提下,为其增加/修改行为。
语法糖:
@decorator # 等价于 func = decorator(func)
def func(...):
...
多个装饰器:
@A
@B
def f(...): pass
# 等价于 f = A(B(f)) ← 先 B 后 A
装饰器在函数定义时(导入时)就会执行,不是调用时。
⸻
- 最小可用装饰器(同步)
import time
from functools import wraps
def timing(func):
@wraps(func) # 关键:保留原函数 __name__/__doc__/签名 等元数据
def wrapper(*args, **kwargs): # 透传所有参数
t0 = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
print(f"{func.__name__} took {time.perf_counter()-t0:.3f}s")
return wrapper
@timing
def work(n): sum(range(n))
work(1_000_000)
务必使用 functools.wraps,否则调试、文档、类型检查都会变差。
⸻
- 带参数的装饰器(“三层结构”)
当我们写 @retry(times=3) 这种带配置的装饰器时,本质是装饰器工厂:
from functools import wraps
import time
def retry(times=3, exceptions=(Exception,), backoff=0.0):
def deco(func):
@wraps(func)
def wrapper(*args, **kwargs):
last = None
for i in range(times):
try:
return func(*args, **kwargs)
except exceptions as e:
last = e
if backoff: time.sleep(backoff*(2**i))
raise last
return wrapper
return deco
@retry(times=5, backoff=0.1)
def may_fail(): ...
三层分别是:工厂 → 真正装饰器 → wrapper。
⸻
- 装饰 async 协程函数
包装协程要用 async def 并 await 原函数:
from functools import wraps
import time, asyncio
def log_async(func):
@wraps(func)
async def wrapper(*args, **kwargs):
t0 = time.perf_counter()
try:
return await func(*args, **kwargs)
finally:
print(f"{func.__name__} took {time.perf_counter()-t0:.3f}s")
return wrapper
@log_async
async def aio():
await asyncio.sleep(0.1)
⸻
- 类装饰器(给“类”加料)
装饰器也能接收并返回类对象:
def add_repr(cls):
def __repr__(self):
fields = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
return f"{cls.__name__}({fields})"
cls.__repr__ = __repr__
return cls
@add_repr
class User:
def __init__(self, name, age): self.name, self.age = name, age
标准库里常见的“类装饰器”:@dataclass, @total_ordering, @enum.unique …
⸻
- 类实现的装饰器(状态更好维护)
当装饰器需要可变状态(例如调用计数、限流)时,用类更清晰:
from functools import wraps
class count_calls:
def __init__(self, func):
wraps(func)(self)
self.func = func
self.n = 0
def __call__(self, *args, **kwargs):
self.n += 1
return self.func(*args, **kwargs)
@count_calls
def foo(): pass
foo(); foo()
# foo.n == 2
⸻
- 方法相关装饰器:@property / @staticmethod / @classmethod
它们本质是描述符/装饰器:
class A:
def __init__(self, x): self._x = x
@property # 像属性一样访问,但有函数逻辑
def x(self): return self._x
@staticmethod # 不接收 self
def ping(): return "pong"
@classmethod # 接收 cls
def make(cls): return cls(0)
⸻
- 类型提示友好写法(保留参数与返回类型)
用 ParamSpec + TypeVar,让装饰器不丢签名类型:
from typing import Callable, ParamSpec, TypeVar
from functools import wraps
P = ParamSpec("P")
R = TypeVar("R")
def timing_typed(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return func(*args, **kwargs)
return wrapper
⸻
- 常见实战装饰器清单
• 性能/可观测:计时、统计 QPS、打点上报
• 健壮性:retry、超时、熔断、限流、缓存(functools.lru_cache)
• 跨领域关注点:鉴权/权限、审计日志、输入校验、事务
• API 层:幂等键、统一异常→HTTP 响应映射
• 单分派:functools.singledispatch(基于参数类型选择实现)
内置好物:
from functools import lru_cache, cached_property, singledispatch
⸻
- 易踩坑与最佳实践
- 务必 @wraps:保留元数据(name/doc/签名)与调试体验。
- 导入时副作用:装饰器在导入时运行,避免做重 IO/重计算。
- 参数透传:wrapper 一律 *args, **kwargs,不要漏参/改名。
- 异常处理:不要“吞异常”;需要时只捕获你预期的异常并加上下文再抛。
- 顺序:多个装饰器叠加时注意 A(B(f)) 的组合顺序。
- 同步/异步匹配:装 async def 就写 async wrapper,否则会把协程对象当返回值。
- 类型安全:用 ParamSpec/TypeVar,避免把函数都变成 Callable[…, Any]。
- 可测试性:把装饰器影响的行为暴露开关(例如 enabled=False、或提供未装饰版本用于测试)。
⸻
一句话收尾
装饰器让你“不改源码、横切增强”:统一把计时/重试/鉴权/缓存/日志等横切逻辑抽离出来;
记住三件宝:@wraps、*args/**kwargs、ParamSpec,写出来的装饰器就既好用又专业。

被折叠的 条评论
为什么被折叠?



