前言
装饰器(Decorator) 是 Python 里的一种特殊工具,它为我们提供了一种在函数外部修改函数的灵活能力。它有点像一顶画着独一无二 @
符号的神奇帽子,只要将它戴在函数头顶上,就能悄无声息的改变函数本身的行为。
你可能已经和装饰器打过不少交道了。在做面向对象编程时,我们就经常会用到 @staticmethod
和 @classmethod
两个内置装饰器。此外,如果你接触过 click 模块,就更不会对装饰器感到陌生。click 最为人所称道的参数定义接口 @click.option(...)
就是利用装饰器实现的。
除了用装饰器,我们也经常需要自己写一些装饰器。在这篇文章里,我将从 最佳实践
和 常见错误
两个方面,来与你分享有关装饰器的一些小知识。
最佳实践
1. 尝试用类来实现装饰器
绝大多数装饰器都是基于函数和 闭包 实现的,但这并非制造装饰器的唯一方式。事实上,Python 对某个对象是否能通过装饰器( @decorator
)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。
-
# 使用 callable 可以检测某个对象是否“可被调用”
-
>>> def foo(): pass
-
...
-
>>> type(foo)
-
<class "function">
-
>>> callable(foo)
-
True
函数自然是“可被调用”的对象。但除了函数外,我们也可以让任何一个类(class)变得“可被调用”(callable)。办法很简单,只要自定义类的 __call__
魔法方法即可。
-
class Foo:
-
def __call__(self):
-
print("Hello, __call___")
-
-
foo = Foo()
-
-
# OUTPUT: True
-
print(callable(foo))
-
# 调用 foo 实例
-
# OUTPUT: Hello, __call__
-
foo()
基于这个特性,我们可以很方便的使用类来实现装饰器。
下面这段代码,会定义一个名为 @delay(duration)
的装饰器,使用它装饰过的函数在每次执行前,都会等待额外的 duration
秒。同时,我们也希望为用户提供无需等待马上执行的 eager_call
接口。
-
import time
-
import functools
-
-
-
class DelayFunc:
-
def __init__(self, duration, func):
-
self.duration = duration
-
self.func = func
-
-
def __call__(self, *args, **kwargs):
-
print(f"Wait for {self.duration} seconds...")
-
time.sleep(self.duration)
-
return self.func(*args, **kwargs)
-
-
def eager_call(self, *args, **kwargs):
-
print("Call without delay")
-
return self.func(*args, **kwargs)
-
-
-
def delay(duration):