Python装饰器需规范实现:权限校验须从标准上下文提取用户、避免硬编码和全局变量;日志装饰器要分离启用开关与记录时机,成对记录并过滤敏感信息;多装饰器按自上而下顺序执行,安全场景应先权限后日志;必须用@wraps保留原函数元信息。

直接在业务函数里写 if not user.has_role("admin") 或手动调用 logging.info,短期能跑通,长期会污染函数签名、重复粘贴逻辑、开关日志还得全局搜索改代码——这不是扩展,是硬编码。
Python 中用 @wraps 写权限装饰器,必须处理好上下文提取
权限校验不是“有没有装饰器”,而是“从哪取用户、角色、请求上下文”。常见错误是假设 current_user 是全局变量,结果在多线程/异步场景下串用户;或把用户对象硬塞进 *args,导致被装饰函数签名不兼容。
- 推荐从标准位置提取:Web 框架(如 Flask 的
g.user、FastAPI 的依赖注入)、CLI 工具的click.Context、或显式传入参数(如@role_required(user_arg="user")) - 不要在装饰器内部做耗时操作(比如查数据库),应由上层准备好
user对象再传入 - 异常类型要明确:
PermissionError用于拒绝访问,ValueError用于参数缺失,避免全捕获Exception
日志装饰器必须区分「记录时机」和「是否启用」两个开关
很多实现把日志开关写死在装饰器定义里(如 @log_execution(enabled=True)),导致无法按环境动态控制。更糟的是,在 try/except 外层记录开始时间,但没考虑函数本身抛出未捕获异常时,结束日志永远不触发。
- 启用开关建议从配置读取:
os.getenv("ENABLE_LOGGING", "false").lower() == "true" - 记录点必须成对:用
time.time()记开始,无论成功失败都记结束;失败时捕获exc_info=True传给logging.error - 避免打印敏感参数:对
kwargs做白名单过滤(如跳过password、token),或统一用repr(arg)[:100]截断
组合多个装饰器时,顺序决定行为优先级
@log_execution 和 @role_required("admin") 叠加后,谁先执行?答案是自上而下:@A @B def f() 等价于 f = A(B(f))。这意味着权限检查应在日志之前发生——否则未授权调用也会留下一条“开始执行”的日志,违反审计要求。
- 典型安全顺序:
@role_required→@log_execution→@cache_result - 如果日志需要包含权限结果(如 “拒绝:用户 role=guest”),那就得让日志装饰器自己调用鉴权逻辑,而不是依赖外层装饰器
- 避免循环依赖:权限装饰器不要反向调用日志函数,日志装饰器也不要触发权限校验
最易被忽略的一点:装饰器返回的 wrapper 函数必须保留原函数的 __name__、__doc__ 和签名,否则 help()、IDE 补全、单元测试 mock 都会出问题。不用 @wraps(func) 的装饰器,等于给自己埋了调试雷。

















