裝飾器 (Decorator) 在 Python 編程中極為常見,可輕松實(shí)現(xiàn) Metadata、Proxy、 AOP 等模式。簡單點(diǎn)說,裝飾器通過返回包裝對象實(shí)現(xiàn)間接調(diào)用,以此來插入額外邏輯。
語法看上去和 Java Annotation、C# Attribute 類似,但不僅僅是添加元數(shù)據(jù)。
>>> @check_args
... def test(*args):
... print args
還原成容易理解的方式:
>>> test = check_args(test)
類似的做法,我們在使用 staticmethod、classmethod 時(shí)就已見過。
>>> def check_args(func):
... def wrap(*args):
... args = filter(bool, args)
... func(*args)
...
... return wrap # 返回 wrap 函數(shù)對象
>>> @check_args # 解釋器執(zhí)行 test = check_args(test)
... def test(*args):
... print args
>>> test # 現(xiàn)在 test 名字與 wrap 關(guān)聯(lián)。
<function wrap at 0x108affde8>
>>> test(1, 0, 2, "", [], 3) # 通過 wrap(test(args)) 完成調(diào)用。
(1, 2, 3)
整個(gè)過程非常簡單:
你完全可以把 "@" 當(dāng)做語法糖,也可以直接使用函數(shù)式寫法。只不過那樣不便于代碼維護(hù),畢竟 AOP 極力避免代碼侵入。
裝飾器不一定非得是個(gè)函數(shù)返回包裝對象,也可以是個(gè)類,通過 call 完成目標(biāo)調(diào)用。
>>> class CheckArgs(object):
... def __init__(self, func):
... self._func = func
...
... def __call__(self, *args):
... args = filter(bool, args)
... self._func(*args)
>>> @CheckArgs # 生成 CheckArgs 實(shí)例。
... def test(*args):
... print args
>>> test # 名字指向該實(shí)例。
<__main__.CheckArgs object at 0x107a237d0>
>>> test(1, 0, 2, "", [], 3) # 每次都是通過該實(shí)例的 __call__ 調(diào)用。
(1, 2, 3)
用類裝飾器對象實(shí)例替代原函數(shù),以后的每次調(diào)用的都是該實(shí)例的 call 方法。這種寫法有點(diǎn)啰嗦,還得注意避免在裝飾器對象上保留狀態(tài)。
Class
為 Class 提供裝飾器同樣簡單,無非是將類型對象做為參數(shù)而已。
>>> 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 函數(shù),可以看做原 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
創(chuàng)建繼承自原類型的 class wrap,然后在 new 里面做手腳就行了。
大多數(shù)時(shí)候,我們僅用裝飾器為原類型增加一些額外成員,那么可直接返回原類型。
>>> def action(cls):
... cls.mvc = staticmethod(lambda: "Action")
... return cls
>>> @action
... class Login(object): pass
>>> Login.mvc()
'Action'
這就是典型的 metaprogramming 做法了。
參數(shù)
參數(shù)讓裝飾器擁有變化,也更加靈活。只是需要兩步才能完成:先傳參數(shù),后送類型。
>>> 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'
只比無參數(shù)版本多了傳遞參數(shù)的調(diào)用,其他完全相同。
User = (table("t_user"))(User)
嵌套
可以在同一目標(biāo)上使用多個(gè)裝飾器。
>>> def A(func):
... print "A"
... return func
>>> def B(func):
... print "B"
... return func
>>> @A
... @B
... def test():
... print "test"
B
A
分解一下,無非是函數(shù)嵌套調(diào)用。
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__ # 山寨貨連個(gè)說明書都木有!
一旦 test 的調(diào)用者要檢查某些特殊屬性,那么這個(gè) wrap 就會(huì)暴露了。幸好有 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 是裝飾器的裝飾器,它的作用是將原函數(shù)對象的指定屬性復(fù)制給包裝函數(shù)對象,默認(rèn)有 module、name、doc,或者通過參數(shù)選擇。
想想看裝飾器都能干嘛?