推理基础设施 · Python1.6 装饰器入门
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

理解它的关键是两步

  1. @装饰器名 等价于 业务函数 = 装饰器名(业务函数)
  2. 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,打印被装饰函数的:

  1. 函数名(用 func.__name__
  2. 传入的关键字参数(注意:是 kwargs)

形如:[log_call] name=xxx kwargs={'k': 'v'}

然后用它装饰一个函数 infer(prompt, **opts),函数体随便返回个字典。

💡 思路提示

点开看提示
  1. wrapper(*args, **kwargs):一并接收位置和关键字参数
  2. func.__name__ 是函数对象自带的属性,保存函数名
  3. 你只用 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

看懂基础版再看这一段。