装饰器 (Decorator) 在 Python 编程中极为常见,可轻松实现 Metadata、Proxy、 AOP 等模式。简单点说,装饰器通过返回包装对象实现间接调用,以此来插入额外逻辑。语法看上去和 Java Annotation、C# Attribute 类似,但不仅仅是添加元数据。
>>> @check_args
... def test(*args):
... print args
还原成容易理解的方式:
>>> test = check_args(test)
类似的做法,我们在使用 staticmethod、classmethod 时就已见过。
>>> def check_args(func):
... def wrap(*args):
... args = filter(bool, args)
... func(*args)
...
... return wrap # 返回 wrap 函数对象
>>> @check_args # 解释器执行 test = check_args(test)
... def test(*args):
... print args
>>> test # 现在 test 名字与 wrap 关联。
<function wrap at 0x108affde8>
>>> test(1, 0, 2, "", [], 3) # 通过 wrap(test(args)) 完成调用。
(1, 2, 3)
整个过程非常简单:
- 将目标函数对象 test 作为参数传递给装饰器 check_args。
- 装饰器返回包装函数 wrap 实现对 test 的间接调用。
- 原函数名字 test 被重新关联到 wrap,所有对该名字的调用实际都是调用 wrap。
你完全可以把 "@" 当做语法糖,也可以直接使用函数式写法。只不过那样不便于代码维护,毕竟 AOP 极力避免代码侵入。装饰器不一定非得是个函数返回包装对象,也可以是个类,通过 call 完成目标调用。
>>> class CheckArgs(object):
... def __init__(self, func):
... self._func = func
...
... def __call__(self, *args):
... args = filter(bool, args)
... self._func(*args)
>>> @CheckArgs # 生成 CheckArgs 实例。
... def test(*args):
... print args
>>> test # 名字指向该实例。
<__main__.CheckArgs object at 0x107a237d0>
>>> test(1, 0, 2, "", [], 3) # 每次都是通过该实例的 __call__ 调用。
(1, 2, 3)
用类装饰器对象实例替代原函数,以后的每次调用的都是该实例的 call 方法。这种写法有点啰嗦,还得注意避免在装饰器对象上保留状态。