higher-order function

closure

decorator

装饰器是一种通过包装目标函数来修改其行为的特殊高阶函数,绝大多数装饰器是利用函数的闭包原理实现的。
装饰器的优势并不在于它提供了动态修改函数的能力,而在于它把影响函数的装饰行为移到了函数头部,降低了代码的阅读与理解成本
为了充分发挥这个优势,装饰器特别适合用来实现以下功能。

(1) 运行时校验:在执行阶段进行特定校验,当校验通不过时终止执行。
适合原因:装饰器可以方便地在函数执行前介入,并且可以读取所有参数辅助校验。
代表样例:Django 框架中的用户登录态校验装饰器 @login_required。

(2) 注入额外参数:在函数被调用时自动注入额外的调用参数。
适合原因:装饰器的位置在函数头部,非常靠近参数被定义的位置,关联性强。
代表样例:unittest.mock 模块的装饰器 @patch。

(3) 缓存执行结果:通过调用参数等输入信息,直接缓存函数执行结果。
适合原因:添加缓存不需要侵入函数内部逻辑,并且功能非常独立和通用。
代表样例:functools 模块的缓存装饰器 @lru_cache。

(4) 注册函数:将被装饰函数注册为某个外部流程的一部分。
适合原因:在定义函数时可以直接完成注册,关联性强。
代表样例:Flask 框架的路由注册装饰器 @app.route。

(5) 替换为复杂对象:将原函数(方法)替换为更复杂的对象,比如类实例或特殊的描述符对象)。
适合原因:在执行替换操作时,装饰器语法天然比 foo = staticmethod(foo) 的写法要直观得多。
代表样例:静态类方法装饰器 @staticmethod。

(1) 基础与技巧
装饰器最常见的实现方式,是利用闭包原理通过多层嵌套函数实现
在实现装饰器时,请记得使用 wraps() 更新包装函数的元数据
wraps() 不光可以保留元数据,还能保留包装函数的额外属性
利用仅限关键字参数,可以很方便地实现可选参数的装饰器

(2) 使用类来实现装饰器
只要是可调用的对象,都可以用作装饰器
实现了 call 方法的类实例可调用
基于类的装饰器分为两种:“函数替换”与“实例替换”
“函数替换”装饰器与普通装饰器没什么区别,只是嵌套层级更少
通过类来实现“实例替换”装饰器,在管理状态和追加行为上有天然的优势
混合使用类和函数来实现装饰器,可以灵活满足各种场景

(3) 使用 wrapt 模块
使用 wrapt 模块可以方便地让装饰器同时兼容函数和类方法
使用 wrapt 模块可以帮你写出结构更扁平的装饰器代码

(4) 装饰器设计技巧
装饰器将包装调用提前到了函数被定义的位置,它的大部分优点也源于此
在编写装饰器时,请考虑你的设计是否能很好发挥装饰器的优势
在某些场景下,类装饰器可以替代元类,并且代码更简单
装饰器和装饰器模式截然不同,不要弄混它们
装饰器里应该只有一层浅浅的包装代码,要把核心逻辑放在其他函数与类中