Skip to content

装饰器与代理

装饰器语法与原理、lru_cache、代理模式、与 Java AOP 的对比。

Updated View as Markdown
For humans

装饰器与代理

装饰器是 Python 的招牌语法,也是代理模式的天然实现。面试主线:装饰器原理、lru_cache 缓存代理、代理模式的三种形态、和 Java AOP 的区别。

装饰器原理

装饰器本质是接收函数、返回函数的函数(闭包):

import functools

def logged(func):
    @functools.wraps(func)          # 保留原函数元信息
    def wrapper(*args, **kwargs):
        print(f"call {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logged
def add(a, b):
    return a + b

要点:

  • @logged 等价于 add = logged(add),函数对象被替换为包装层
  • functools.wraps 复制 __name____doc__ 等,否则调试和文档全乱
  • 带参装饰器(如 @cache(ttl=60))是装饰器工厂:外层函数返回装饰器
  • 多个装饰器从下往上包装,执行时从上往下(洋葱)

lru_cache:缓存代理

from functools import lru_cache

@lru_cache(maxsize=128)
def get_user(user_id: int):
    return db.query(user_id)
  • 按参数缓存返回值,命中直接返回(代理模式:替真实对象挡请求
  • 应用:昂贵计算、重复查询、FastAPI 依赖单例(见单例篇)
  • 注意:参数必须可哈希;缓存的是返回值,对象可变时要小心

代理模式的三种形态

形态 意图 Python 写法
虚拟代理 延迟创建昂贵对象 惰性属性(@property 首次访问才建)
缓存代理 缓存重复结果 lru_cache
保护代理 控制访问权限 权限校验装饰器(@require_auth
远程代理 本地接口访问远端 requests 客户端封装、RPC stub
def require_auth(func):
    @functools.wraps(func)
    def wrapper(request, *args, **kwargs):
        if not request.user:
            raise PermissionError("未登录")
        return func(request, *args, **kwargs)
    return wrapper

与 Java AOP 的对比

维度 Python 装饰器 Java AOP(Spring)
织入方式 运行时直接包装函数 代理对象(JDK 动态代理/CGLIB)
声明位置 函数上方装饰器语法 注解 + 切面配置
粒度 函数级,显式 切点表达式批量匹配(类/方法/包)
横切能力 手动写 通知类型齐全(Before/After/环绕)
反射依赖 依赖代理机制

共同点:都是在不改原代码的前提下增强行为(日志、鉴权、事务)。差异:Python 装饰器是显式逐函数标注,AOP 是声明式批量织入。Python 的横切可以用装饰器组合模拟,但没有 AOP 的切点表达式。

面试追问

  1. 装饰器实现原理? 闭包:外层接收函数返回包装函数。@ 语法就是赋值替换
  2. functools.wraps 为什么必要? 不复制元信息,包装后 name/doc 丢失,调试和文档受损
  3. lru_cache 是代理吗? 是缓存代理:参数命中直接返回缓存,不调用原函数
  4. 装饰器和 AOP 的区别? 装饰器显式逐函数、运行时包装;AOP 声明式批量织入、切点匹配。Python 没有 AOP 容器
  5. 多个装饰器的执行顺序? 从下往上包装,从上往下执行(洋葱模型)。装饰顺序影响执行顺序
Navigation

Type to search…

↑↓ navigate↵ selectEsc close