装饰器 (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):
122
... 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
123
将 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):
124
... 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))
125
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__,或者通过参数选择。
126
???
想想看装饰器都能干嘛?
• AOP: ⾝身份验证、参数检查、异常⽇日志等等。
• Proxy: 对⺫⽬目标函数注⼊入权限管理等。
• Context: 提供函数级别的上下⽂文环境,⽐比如 Synchronized(func) 同步。
• Caching: 先检查缓存是否过期,然后再决定是否调⽤用⺫⽬目标函数。
• Metaprogramming: 这个⾃自不必多说了。
• 等等……
简单点说,装饰器通过返回包装对象实现间接调⽤用,以此来插⼊入额外逻辑。
语法看上去和 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):
122
... 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
123
将 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):
124
... 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))
125
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__,或者通过参数选择。
126
???
想想看装饰器都能干嘛?
• AOP: ⾝身份验证、参数检查、异常⽇日志等等。
• Proxy: 对⺫⽬目标函数注⼊入权限管理等。
• Context: 提供函数级别的上下⽂文环境,⽐比如 Synchronized(func) 同步。
• Caching: 先检查缓存是否过期,然后再决定是否调⽤用⺫⽬目标函数。
• Metaprogramming: 这个⾃自不必多说了。
• 等等……