装饰器是 Python 最强大的语法糖之一,也是 AI 工程化离不开的利器。从 @retry 重试机制到 @timer 性能监控,从 Agent 链路追踪到 LLM 调用缓存——装饰器让这些横切关注点(Cross-Cutting Concerns)的实现变得优雅而简洁。
一、闭包:函数记住外部变量的魔法 1.1 什么是自由变量? 当我们在一个函数内部引用外部作用域的变量时,这个变量对函数来说就是自由变量(Free Variable) 。
1 2 3 4 5 6 7 def make_adder (n ): def adder (x ): return x + n return adder add5 = make_adder(5 ) print (add5(10 ))
add5 不仅仅是一个函数,它携带了创建时捕获的上下文 。
1.2 闭包细胞(Closure Cell) Python 通过闭包细胞(Closure Cell) 实现这一机制:
1 2 print (add5.__closure__) print (add5.__code__.co_freevars)
闭包细胞是 Python 为每个被捕获的外部变量创建的不可变容器。即使外部函数已经返回,内部函数依然能访问这些变量。
1.3 LEGB 规则:Python 变量查找顺序 Python 的名字查找遵循 LEGB 规则:
graph TD
L["L - Local<br/>函数内部定义的变量"]
E["E - Enclosing<br/>外层函数的 locals()"]
G["G - Global<br/>模块级全局变量"]
B["B - Built-in<br/>Python 内置函数/异常等"]
L --> E --> G --> B
L -.->|"最优先"| L
B -.->|"最后查找"| B
style L fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#FFDAB9,stroke:#FFAB76,color:#333
style G fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 x = "global" def outer (): x = "enclosing" def inner (): x = "local" print (x) inner() print (x) print (x)
1.4 闭包的实际用途:记忆化 闭包可以创建记忆化(Memoization) 缓存:
1 2 3 4 5 6 7 8 9 10 11 12 def memoize (): cache = {} def wrapper (n ): if n not in cache: cache[n] = n ** 2 return cache[n] return wrapper fast = memoize() print (fast(5 )) print (fast(5 ))
二、装饰器:修改函数行为的利器 2.1 装饰器的本质: Higher-Order Function 装饰器(Decorator) 本质上是一个接收函数并返回新函数的高阶函数(Higher-Order Function):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def timer (func ): def wrapper (*args, **kwargs ): start = time.perf_counter() result = func(*args, **kwargs) print (f"{func.__name__} : {time.perf_counter()-start:.4 f} s" ) return result return wrapper def slow_api_call (prompt: str ) -> str : time.sleep(0.1 ) return f"Response to: {prompt} " slow_api_call = timer(slow_api_call)
2.2 @ 语法糖展开 @decorator 只是上述手动包装的语法简化 :
1 2 3 4 5 6 7 8 9 10 11 12 13 def slow_api_call (prompt: str ) -> str : time.sleep(0.1 ) return f"Response to: {prompt} " slow_api_call = timer(slow_api_call) @timer def slow_api_call (prompt: str ) -> str : time.sleep(0.1 ) return f"Response to: {prompt} "
装饰器替换函数时,会丢失原函数的 __name__、__doc__ 等元信息。functools.wraps 帮你解决这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import functoolsimport timefrom typing import Callable , TypeVar, ParamSpecP = ParamSpec('P' ) R = TypeVar('R' ) def timer (func: Callable [P, R] ) -> Callable [P, R]: @functools.wraps(func ) def wrapper (*args: P.args, **kwargs: P.kwargs ) -> R: start = time.perf_counter() result = func(*args, **kwargs) print (f"{func.__name__} : {time.perf_counter()-start:.4 f} s" ) return result return wrapper @timer def slow_api_call (prompt: str ) -> str : """模拟 AI API 调用""" time.sleep(0.1 ) return f"Response to: {prompt} " print (slow_api_call.__name__) print (slow_api_call.__doc__)
三、带参数的装饰器:三层嵌套函数 如果装饰器需要接收参数 (如 @retry(max_attempts=3)),需要再包裹一层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def retry (max_attempts: int = 3 ): """第一层:接收装饰器参数""" def decorator (func: Callable [P, R] ) -> Callable [P, R]: """第二层:接收被装饰的函数""" @functools.wraps(func ) def wrapper (*args: P.args, **kwargs: P.kwargs ) -> R: """第三层:执行实际的包装逻辑""" last_exc = None for attempt in range (max_attempts): try : return func(*args, **kwargs) except Exception as e: last_exc = e if attempt < max_attempts - 1 : time.sleep(0.1 * (attempt + 1 )) raise last_exc return wrapper return decorator @retry(max_attempts=3 ) def unreliable_api (): """模拟不稳定的 AI API""" import random if random.random() < 0.7 : raise ConnectionError("Network error" ) return "Success"
三层嵌套的执行顺序 flowchart TD
START["@retry(max_attempts=3)"] --> L1["第一层<br/>retry(max_attempts=3)<br/>返回 decorator"]
L1 --> L2["第二层<br/>decorator(func)<br/>返回 wrapper"]
L2 --> L3["第三层<br/>wrapper(*args, **kwargs)<br/>执行实际逻辑"]
L3 --> END["返回结果或抛出异常"]
style START fill:#C7CEEA,stroke:#9FA8DA
style L1 fill:#FFDAB9,stroke:#FFAB76
style L2 fill:#FFDAB9,stroke:#FFAB76
style L3 fill:#B5EAD7,stroke:#80CBC4
style END fill:#FFB3C6,stroke:#F48FB1四、类装饰器:使用 __call__ 封装状态 装饰器不一定是函数,任何可调用对象 都可以作为装饰器,包括类 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class CallTracker : def __init__ (self, func: Callable [P, R] ): functools.update_wrapper(self , func) self .func = func self .count = 0 def __call__ (self, *args: P.args, **kwargs: P.kwargs ) -> R: self .count += 1 print (f"Called {self.func.__name__} #{self.count} " ) return self .func(*args, **kwargs) @CallTracker def generate (prompt: str ) -> str : """模拟 LLM 生成""" return f"Generated: {prompt} " generate("Hello" ) generate("World" )
类装饰器可以维护状态 (如调用次数),这是函数装饰器难以优雅实现的场景。
五、装饰器链:多个装饰器的执行顺序 多个装饰器可以叠加,形成装饰器链 :
1 2 3 4 5 @memoize @timer @retry(max_attempts=3 ) def complex_ai_task (prompt: str ) -> str : ...
执行顺序详解 flowchart LR
subgraph "定义时(从下到上)"
A["@retry"] --> B["@timer"] --> C["@memoize"]
end
subgraph "调用时(从外到内)"
D["retry 包装层"] --> E["timer 包装层"] --> F["memoize 包装层"] --> G["原函数"]
end
subgraph "返回时(从内到外)"
G --> F_result --> E_result --> D_result
end
style A fill:#FFB3C6,stroke:#F48FB1
style B fill:#FFDAB9,stroke:#FFAB76
style C fill:#B5EAD7,stroke:#80CBC4核心原则 :
定义顺序 :装饰器从上到下应用调用顺序 :从外向内执行返回顺序 :从内向外传递结果六、AI 实战:构建 Agent 链路追踪器 在 AI Agent 开发中,链路追踪(Tracing) 是调试和监控的核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class AgentTracer : def __init__ (self ): self .steps = [] def trace (self, func: Callable [P, R] ) -> Callable [P, R]: @functools.wraps(func ) def wrapper (*args: P.args, **kwargs: P.kwargs ) -> R: step = {"name" : func.__name__, "args" : args, "status" : "running" } self .steps.append(step) try : result = func(*args, **kwargs) step["status" ] = "success" return result except Exception as e: step["status" ] = "error" step["error" ] = str (e) raise return wrapper def report (self ): for s in self .steps: icon = "✅" if s["status" ] == "success" else "❌" print (f"{icon} {s['name' ]} : {s['status' ]} " ) tracer = AgentTracer() @tracer.trace def think (reasoning: str ) -> str : """Agent 思考步骤""" return f"Thinking: {reasoning} " @tracer.trace def act (action: str ) -> str : """Agent 行动步骤""" return f"Acting: {action} " if __name__ == "__main__" : think("Should I call the API?" ) act("Calling LLM" ) tracer.report()
输出 :
1 2 ✅ think: success ✅ act: success
七、对比总结:装饰器 vs 高阶函数 vs Monkey Patching 维度 装饰器 高阶函数 Monkey Patching 定义时 @decorator 语法糖显式传递函数 修改已存在函数 侵入性 低(可选叠加) 中(调用方需传递) 高(全局影响) 可组合性 ✅ 链式叠加 ✅ 组合使用 ❌ 难以叠加 元信息保留 @wraps 保留❌ 丢失 ❌ 丢失 状态维护 类装饰器可实现 ❌ 通常无状态 ❌ 需手动维护 典型用途 横切关注点 数据转换 库扩展/测试 Mock
八、常见 AI 场景装饰器一览 装饰器 用途 实现要点 @timer性能监控 记录执行时间 @retry容错重试 捕获异常 + 循环 @cache结果缓存 字典记忆化 @trace链路追踪 记录步骤状态 @rate_limit限流控制 时间窗口计数 @validate输入验证 参数类型检查
九、完整可运行代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import functoolsimport timefrom typing import Callable , TypeVar, ParamSpecP = ParamSpec('P' ) R = TypeVar('R' ) def make_adder (n ): def adder (x ): return x + n return adder add5 = make_adder(5 ) print (add5(10 )) print (add5.__closure__) def timer (func: Callable [P, R] ) -> Callable [P, R]: @functools.wraps(func ) def wrapper (*args: P.args, **kwargs: P.kwargs ) -> R: start = time.perf_counter() result = func(*args, **kwargs) print (f"{func.__name__} : {time.perf_counter()-start:.4 f} s" ) return result return wrapper @timer def slow_api_call (prompt: str ) -> str : time.sleep(0.1 ) return f"Response to: {prompt} " def retry (max_attempts: int = 3 ): def decorator (func: Callable [P, R] ) -> Callable [P, R]: @functools.wraps(func ) def wrapper (*args: P.args, **kwargs: P.kwargs ) -> R: last_exc = None for attempt in range (max_attempts): try : return func(*args, **kwargs) except Exception as e: last_exc = e if attempt < max_attempts - 1 : time.sleep(0.1 * (attempt + 1 )) raise last_exc return wrapper return decorator @retry(max_attempts=3 ) def unreliable_api (): import random if random.random() < 0.7 : raise ConnectionError("Network error" ) return "Success" class CallTracker : def __init__ (self, func: Callable [P, R] ): functools.update_wrapper(self , func) self .func = func self .count = 0 def __call__ (self, *args: P.args, **kwargs: P.kwargs ) -> R: self .count += 1 print (f"Called {self.func.__name__} #{self.count} " ) return self .func(*args, **kwargs) @CallTracker def generate (prompt: str ) -> str : return f"Generated: {prompt} " class AgentTracer : def __init__ (self ): self .steps = [] def trace (self, func: Callable [P, R] ) -> Callable [P, R]: @functools.wraps(func ) def wrapper (*args: P.args, **kwargs: P.kwargs ) -> R: step = {"name" : func.__name__, "args" : args, "status" : "running" } self .steps.append(step) try : result = func(*args, **kwargs) step["status" ] = "success" return result except Exception as e: step["status" ] = "error" step["error" ] = str (e) raise return wrapper def report (self ): for s in self .steps: icon = "✅" if s["status" ] == "success" else "❌" print (f"{icon} {s['name' ]} : {s['status' ]} " ) tracer = AgentTracer() @tracer.trace def think (reasoning: str ) -> str : return f"Thinking: {reasoning} " @tracer.trace def act (action: str ) -> str : return f"Acting: {action} " if __name__ == "__main__" : think("Should I call the API?" ) act("Calling LLM" ) tracer.report()
核心要点 :闭包是装饰器的基础,装饰器是闭包最优雅的应用场景之一。在 AI 开发中善用装饰器,可以让日志、监控、重试、缓存等横切关注点与业务逻辑分离,写出更干净、更可维护的代码。
原创于 2026-04-25 | 所属系列:【Python AI教程】 📚 Python AI教程 系列导航 本文是《Python AI教程》系列第 1/14 篇。
📖 全部 14 篇目录(点击展开) (一)闭包与装饰器 ← 当前 (二)上下文管理器 (三)生成器与迭代器 (四)类型提示 (五)Dataclass 与 attrs (六)async/await (七)Threading 与 Multiprocessing (八)函数式编程 (九)描述符协议 (十)元类 (十一)Protocol与结构化类型 (十二)异常链与日志 (十三)缓存艺术 (十四)组合模式实战