狙击手挑战
105.14M · 2026-03-12
前言:我们理解了装饰器的地基——闭包。理解了闭包,你已经看穿了装饰器“偷梁换柱”的本质。但在真正的工程实践中,仅仅能写出一个简单的装饰器是不够的。
如果你在生产环境直接使用上一篇提到的简易装饰器,你会遇到两个致命问题:函数的身份迷失(元数据丢失) ,以及无法灵活传参。
今天,我们要拿起工程化的“手术刀”,把装饰器从一个有趣的语法技巧,升级为一套稳健的面向切面编程(AOP)工具。
__name__当你给一个函数戴上“装饰器”这顶帽子时,这个函数其实已经不再是原来的它了。
Python
def logger(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
"""计算两个数的和"""
return a + b
print(add.__name__) # 输出: wrapper (而不是 add!)
print(add.__doc__) # 输出: None (丢失了文档字符串!)
为什么会这样? 还记得我们说的吗?@logger 等价于 add = logger(add)。此时,变量名 add 指向的其实是 logger 内部定义的 wrapper 函数。
在小型脚本中这或许无伤大雅,但在大型工程中,这会产生灾难性后果:自动化文档工具(如 Sphinx)失效、调试时的堆栈跟踪(Stack Trace)乱码、甚至某些依赖函数名称的逻辑会直接崩溃。
functools.wraps为了修补这个漏洞,Python 官方提供了一个极其优雅的解决方案:functools.wraps。它本质上也是一个装饰器,作用是将原函数的元数据(名称、文档、参数列表等)“拷贝”给 wrapper。
Python
from functools import wraps
def logger(func):
@wraps(func) # 关键一步:保住原函数的灵魂
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
"""计算两个数的和"""
return a + b
print(add.__name__) # 输出: add
print(add.__doc__) # 输出: 计算两个数的和
工程建议: 除非你有特殊需求要隐藏原函数信息,否则写装饰器的第一行代码,永远应该是 @wraps(func)。
这是装饰器学习中最难的一道坎。如果我们需要根据不同的配置来控制装饰器的行为(例如:@retry(times=3) 或 @permission(role='admin')),简单的两层嵌套就不够用了。
你需要构建一套**“三层嵌套结构”**。
我们可以将带参数的装饰器想象成一个“三层套娃”:
func)。*args, **kwargs),并执行核心增强逻辑。假设我们要为一个不稳定的网络请求函数编写一个自动重试工具:
Python
import time
from functools import wraps
# 第一层:配置层,接收重试次数
def retry(times=3, delay=1):
# 第二层:包装层,接收被装饰的函数
def decorator(func):
# 第三层:执行层,真正的逻辑增强
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for i in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"尝试第 {i+1} 次失败,等待 {delay}s...")
time.sleep(delay)
# 达到重试极限后抛出异常
raise last_exception
return wrapper
return decorator
@retry(times=5, delay=2) # 先调用 retry(5, 2) 返回一个 decorator,再进行装饰
def fetch_data():
import random
if random.random() < 0.8:
raise ConnectionError("网络波动")
return "成功获取数据"
很多开发者会被这三层 return 绕晕。其实逻辑非常清晰:
@retry(times=5) 时,Python 先运行 retry(times=5),得到那个名为 decorator 的函数。@decorator 逻辑,将 fetch_data 传给它,得到 wrapper。fetch_data() 时,你实际上是在调用 wrapper,它通过闭包捕获了最外层的 times 和中间层的 func。在生产环境下,我们经常需要监控各个接口的响应速度。这是一个标准的 面向切面(AOP) 场景。
Python
import time
import logging
from functools import wraps
def time_monitor(threshold=0.5):
"""
监控函数执行耗时,如果超过阈值则记录警告日志
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
duration = end_time - start_time
if duration > threshold:
logging.warning(
f"接口耗时预警 | 函数: {func.__name__} | "
f"耗时: {duration:.4f}s | 阈值: {threshold}s"
)
return result
return wrapper
return decorator
@time_monitor(threshold=0.1)
def query_database():
time.sleep(0.2) # 模拟慢查询
return "Data"
func)与环境配置(times, threshold)彻底解耦。__init__ 接收配置,通过 __call__ 进行包装,代码可读性有时会比三层嵌套更好。从“能用”到“专业”的跨越,就在于你对细节的把控。functools.wraps 是对原函数的尊重,而三层嵌套则是对逻辑灵活性的追求。
掌握了这两点,你已经可以自信地在生产代码中使用装饰器,构建起整洁、解耦、且极具扩展性的系统架构。
Vite 凭什么比 Webpack 快50%?揭秘闪电构建背后的黑科技
我用 OpenClaw 搭了一套运营 Agent,每天自动生产内容、分发、追踪数据——独立开发者的运营平替
2026-03-12
2026-03-12