01
函数与代码组织 · 6 / 6
1.6 装饰器入门
💡 推理服务每个 handler 都套着日志 / 计时 / 鉴权 / 限流的装饰器
1.6 装饰器入门
装饰器 = 包裹函数的函数
def 装饰器名(func):
def wrapper(*args, **kwargs):
# 前置逻辑
result = func(*args, **kwargs)
# 后置逻辑
return result
return wrapper
@装饰器名
def 业务函数():
...
推理场景里装饰器用来干嘛
- 计时:算一次推理耗时
- 日志:记录请求入参、响应、调用方
- 异常兜底:捕获所有异常,返回统一错误结构
- 鉴权:检查 API key
理解它的关键是两步
@装饰器名等价于业务函数 = 装饰器名(业务函数)wrapper把原函数包了一层,前后可以塞逻辑
示范
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"[{func.__name__}] took {(time.time()-start)*1000:.1f}ms")
return result
return wrapper
@timer
def fake_infer(prompt):
time.sleep(0.05)
return f"[fake] {prompt}"
print(fake_infer("hello"))
✍️ 练习
写一个装饰器 log_call,打印被装饰函数的:
- 函数名(用
func.__name__) - 传入的关键字参数(注意:是 kwargs)
形如:[log_call] name=xxx kwargs={'k': 'v'}
然后用它装饰一个函数 infer(prompt, **opts),函数体随便返回个字典。
💡 思路提示
点开看提示
wrapper(*args, **kwargs):一并接收位置和关键字参数func.__name__是函数对象自带的属性,保存函数名- 你只用 kwargs,所以打印 kwargs 即可
✅ 参考解法
写不出来再打开
def log_call(func):
def wrapper(*args, **kwargs):
print(f"[log_call] name={func.__name__} kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@log_call
def infer(prompt, **opts):
return {"prompt": prompt, "opts": opts, "output": "[fake]"}
print(infer("hi", temperature=0.0, max_tokens=64))
🔍 进阶思考
现在 wrapper 签名丢失了原函数的元信息(函数名、文档字符串)。functools.wraps(func) 可以保留。
import functools
def log_call(func):
@functools.wraps(func) # ← 加这一行
def wrapper(*args, **kwargs):
print(f"[log_call] name={func.__name__} kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
看懂基础版再看这一段。