题目重点:解释函数替换语义、闭包和wraps,区分同步与异步包装,并避免共享状态污染。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 练习为同步函数装饰器,示例记录调用前后事件;异步/生成器另确认生命周期。
解题路线
- @decorator是定义时把函数传给decorator并用返回值重新绑定名字,等价f=decorator(f);不是每次调用前都重新执行整个装饰器构造。
- 常见返回wrapper闭包,闭包持有原函数与配置;wrapper在调用时做鉴权、计时或重试再调用原函数。参数需*args/**kwargs透传并返回原结果,不能无意丢掉关键字参数或返回值。
- functools.wraps保留名字、文档及__wrapped__等元数据,改善调试、检查和测试框架识别;它不保证签名与执行语义完全不变,也不把错误装饰器自动修好。
- 副作用发生时机要明确:普通同步计时包装可测完整调用;async函数若仅返回coroutine,测到的是创建耗时,正确包装通常需要await。生成器函数也要看迭代过程而非对象创建。
- 重试装饰器会重复副作用,只有满足幂等或限定异常条件才能使用;鉴权缓存必须绑定正确用户上下文,不能在共享闭包里泄露上个请求身份。
- 测试参数透传、返回值、异常传播、元数据、嵌套顺序和并发共享状态。@outer @inner调用顺序是outer(inner(f)),清理逻辑应用finally保证异常路径执行。
手动走一遍
trace包装add(a,b=0),add(2,b=3)返回5并记录before/after,add.__name__保持add。若原函数抛错而没有finally,after不会记录;这是合同差异,需要专门覆盖。
规模与取舍
包装常量逻辑通常每次O(1)额外开销,计时/重试/缓存策略按实际操作另计;重试r次可能使原工作量乘r,缓存占用依保留键数。
易错点与追问
容易失分的边界
- 丢kwargs或返回值。
- 把装饰器构造时间与函数调用时间混淆。
- async只计coroutine创建。
继续深挖
- 有参数的装饰器为何多一层函数?
- 嵌套顺序如何测?
- 重试遇到非幂等动作怎么办?
