装饰器
装饰器 (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 方法。这种写法有点啰嗦,还得注意避免在装饰器对象上保留状态。
Class
为 Class 提供装饰器同样简单,无非是将类型对象做为参数而已。
>>> def singleton(cls):
... def wrap(*args, **kwargs):
... o = getattr(cls, "__instance__", None)
... if not o:
... o = cls(*args, **kwargs)
... cls.__instance__ = o
...
... return o
...
... return wrap # 返回 wrap 函数,可以看做原 class 的工厂方法。
>>> @singleton
... class A(object):
... def __init__(self, x):
... self.x = x
>>> A
<function wrap at 0x108afff50>
>>> a, b = A(1), A(2)
>>> a is b
True
将 class A 替换成 func wrap 可能有些不好看,修改一下,返回 class wrap。
>>> def singleton(cls):
... class wrap(cls):
... def __new__(cls, *args, **kwargs):
... o = getattr(cls, "__instance__", None)
... if not o:
... o = object.__new__(cls)
... cls.__instance__ = o
...
... return o
...
... return wrap
>>> @singleton
... class A(object):
... def test(self): print hex(id(self))
>>> a, b = A(), A()
>>> a is b
True
>>> a.test()
0x1091e9990
创建继承自原类型的 class wrap,然后在 new 里面做手脚就行了。
大多数时候,我们仅用装饰器为原类型增加一些额外成员,那么可直接返回原类型。
>>> def action(cls):
... cls.mvc = staticmethod(lambda: "Action")
... return cls
>>> @action
... class Login(object): pass
>>> Login.mvc()
'Action'
这就是典型的 metaprogramming 做法了。
参数
参数让装饰器拥有变化,也更加灵活。只是需要两步才能完成:先传参数,后送类型。
>>> def table(name):
... def _table(cls):
... cls.__table__ = name
... return cls
...
... return _table
>>> @table("t_user")
... class User(object): pass
>>> @table("t_blog")
... class Blog(object): pass
>>> User.__table__
't_user'
>>> Blog.__table__
't_blog'
只比无参数版本多了传递参数的调用,其他完全相同。
User = (table("t_user"))(User)
嵌套
可以在同一目标上使用多个装饰器。
>>> def A(func):
... print "A"
... return func
>>> def B(func):
... print "B"
... return func
>>> @A
... @B
... def test():
... print "test"
B
A
分解一下,无非是函数嵌套调用。
test = A(B(test))
functools.wraps
如果装饰器返回的是包装对象,那么有些东西必然是不同的。
>>> def check_args(func):
... def wrap(*args):
... return func(*filter(bool, args))
...
... return wrap
>>> @check_args
def test(*args):
... """test function"""
... print args
>>> test.__name__ # 冒牌货!
'wrap'
>>> test.__doc__ # 山寨货连个说明书都木有!
一旦 test 的调用者要检查某些特殊属性,那么这个 wrap 就会暴露了。幸好有 functools.wraps。
>>> def check_args(func):
... @functools.wraps(func)
... def wrap(*args):
... return func(*filter(bool, args))
...
... return wrap
>>> @check_args
def test(*args):
"""test function"""
print args
>>> test
<function test at 0x108b026e0>
>>> test.__name__
'test'
>>> test.__doc__
'test function'
>>> test(1, 0, 2, "", 3)
(1, 2, 3)
functools.wraps 是装饰器的装饰器,它的作用是将原函数对象的指定属性复制给包装函数对象,默认有 module、name、doc,或者通过参数选择。
想想看装饰器都能干嘛?
- AOP: 身份验证、参数检查、异常日志等等。
- Proxy: 对目标函数注入权限管理等。
- Context: 提供函数级别的上下文环境,比如 Synchronized(func) 同步。
- Caching: 先检查缓存是否过期,然后再决定是否调用目标函数。
- Metaprogramming: 这个自不必多说了。
- 等等……