先給出一個(gè)四人團(tuán)對Decorator mode的定義:動態(tài)地給一個(gè)對象添加一些額外的職責(zé)。
再來說說這個(gè)模式的好處:認(rèn)證,權(quán)限檢查,記日志,檢查參數(shù),加鎖,等等等等,這些功能和系統(tǒng)業(yè)務(wù)無關(guān),但又是系統(tǒng)所必須的,說的更明白一點(diǎn),就是面向方面的編程(AOP)。
在Python中Decorator mode可以按照像其它編程語言如C++, Java等的樣子來實(shí)現(xiàn),但是Python在應(yīng)用裝飾概念方面的能力上遠(yuǎn)不止于此,Python提供了一個(gè)語法和一個(gè)編程特性來加強(qiáng)這方面的功能。Python提供的語法就是裝飾器語法(decorator),如下:
1
2
3
4
|
@aoo def foo(): pass def aoo(fn): return fn |
裝飾模式強(qiáng)調(diào)動態(tài)地給對象添加額外的功能。 Python內(nèi)置了很多對裝飾器的支持,因此在Python中使用裝飾模式是非常容易的,下面是一個(gè)典型的例子,給函數(shù)增加日志功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import functools def log_wrapper(fun): @functools .wraps(fun) def wrapper( * args, * * kwargs): print '在函數(shù)執(zhí)行前加日志' ret = fun( * args, * * kwargs) print '在函數(shù)執(zhí)行后家日志' return ret return wrapper @log_wrapper def test(): print 'Hello, 世界' |
functools.wraps是Python標(biāo)準(zhǔn)庫提供的一個(gè)特殊的裝飾器,用來解決裝飾器帶來的一些常規(guī)問題,如函數(shù)名稱、doc等的不一致問題。@是Python針對裝飾器提供的一個(gè)語法糖,上面的@log_wrapper相當(dāng)于wrap_test = log_rapper(test),用@后,這個(gè)步驟由解釋器代勞了。
裝飾器是Python編程必須掌握的一項(xiàng)技能,在編碼過程中經(jīng)常會用到。
這里只是一個(gè)普通的內(nèi)嵌函數(shù)
1
2
3
4
5
6
|
def foo(x): y = x def foo1 (): a = 1 return a return foo1 |
而下面boo則是一個(gè)閉包
1
2
3
4
5
6
|
def aoo(a, b): c = a def boo (x): x = b + 1 return x return boo |
boo的特殊性在于引用了外部變量b,當(dāng)aoo返回后,只要返回值(boo)一直存在,則對b的引用就會一直存在。
上面的知識可能需要花些時(shí)間消化,如果你覺得已經(jīng)掌握了這些知識,下面就回歸正題,看看這些語言特性是怎樣來實(shí)現(xiàn)Python中裝飾的概念的。
還是讓我們先看一個(gè)簡單的例子,然后逐步深入。這個(gè)例子就是加鎖,怎樣實(shí)現(xiàn)加鎖的功能?
具體需求是這樣的:我有一個(gè)對象,實(shí)現(xiàn)了某些功能并提供了一些接口供其它模塊調(diào)用,這個(gè)對象是運(yùn)行在并發(fā)的環(huán)境中的,因此我需要對接口的調(diào)用進(jìn)行同步,第一版的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
class Foo( object ): def __init__( self , …): self .lock = threading.Lock() def interface1( self , …): self .lock.acquire() try : do something finally : self .lock.release() def interface2( self , …): same as interface1() … |
這版代碼的問題很明顯,那就是每個(gè)接口函數(shù)都有相同的加鎖/解鎖代碼,重復(fù)的代碼帶來的是更多的鍵入,更多的閱讀,更多的維護(hù),以及更多的修改,最主要的是,程序員本應(yīng)集中在業(yè)務(wù)上的的精力被分散了,而且請注意,真正的業(yè)務(wù)代碼在距離函數(shù)定義2次縮進(jìn)處開始,即使你的顯示器是寬屏,這也會帶來一些閱讀上的困難。
你直覺的認(rèn)為,可以把這些代碼收進(jìn)一個(gè)函數(shù)中,以達(dá)到復(fù)用的目的,但是請注意,這些代碼不是一個(gè)完整同一的代碼塊,而是在中間嵌入了業(yè)務(wù)代碼。
現(xiàn)在我們用裝飾器語法來改進(jìn)這部分代碼,得到第2版代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def sync(func): def wrapper( * args, * * kv): self = args[ 0 ] self .lock.acquire() try : return func( * args, * * kv) finally : self .lock.release() return wrapper class Foo( object ): def __init__( self , …): self .lock = threading.Lock() @sync def interface1( self , …): do something @sync def interface2( self , …): do something … |
一個(gè)裝飾器函數(shù)的第一個(gè)參數(shù)是所要裝飾的那個(gè)函數(shù)對象,而且裝飾器函數(shù)必須返回一個(gè)函數(shù)對象。如sync函數(shù),當(dāng)其裝飾interface1時(shí),參數(shù)func的值就是interface1,返回值是wrapper,但類Foo實(shí)例的interface1被調(diào)用時(shí),實(shí)際調(diào)用的是wrapper函數(shù),在wrapper函數(shù)體中間接調(diào)用實(shí)際的interface1;當(dāng)interface2被調(diào)用時(shí),也調(diào)用的是wrapper函數(shù),不過由于在裝飾時(shí)func已經(jīng)變成interface2,所以會間接地調(diào)用到實(shí)際的interface2函數(shù)。
使用裝飾器語法的好處:
代碼量大大的減少了,更少的代碼意味著更少的維護(hù),更少的閱讀,更少的鍵入,好處不一而足(可復(fù)用,可維護(hù))
用戶基本上將絕大部分精力放在了業(yè)務(wù)代碼上,而且少了加減鎖的代碼,可讀性也提高了
缺點(diǎn):
業(yè)務(wù)對象Foo中有一個(gè)非業(yè)務(wù)數(shù)據(jù)成員lock,很礙眼;
相當(dāng)程度的耦合,wrapper的第一個(gè)參數(shù)必須是對象本身,而且被裝飾的對象中必須有一個(gè)lock對象存在,這給客戶對象添加了限制,使用起來不是很舒服。
我們可以更進(jìn)一步想一想:
lock對象必須要放在Foo中嗎?
為每個(gè)接口函數(shù)都鍵入@sync還是很煩人的重復(fù)性人工工作,如果漏添加一個(gè),還是會造成莫名其妙的運(yùn)行時(shí)錯(cuò)誤,為什么不集中處理呢?
為了解決上述的缺點(diǎn),第3版代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class DecorateClass( object ): def decorate( self ): for name, fn in self . iter (): if not self . filter (name, fn): continue self .operate(name, fn) class LockerDecorator(DecorateClass): def __init__( self , obj, lock = threading.RLock()): self .obj = obj self .lock = lock def iter ( self ): return [(name, getattr ( self .obj, name)) for name in dir ( self .obj)] def filter ( self , name, fn): if not name.startswith( '_' ) and callable (fn): return True else : return False def operate( self , name, fn): def locker( * args, * * kv): self .lock.acquire() try : return fn( * args, * * kv) finally : self .lock.release() setattr ( self .obj, name, locker) class Foo( object ): def __init__( self , …): … LockerDecorator( self ).decorate() def interface1( self , …): do something def interface2( self , …): do something … |
對對象的功能裝飾是一個(gè)更一般的功能,不僅限于為接口加鎖,我用2個(gè)類來完成這一功能,DecorateClass是一個(gè)基類,只定義了遍歷并應(yīng)用裝飾功能的算法代碼(template method),LockerDecorator實(shí)現(xiàn)了為對象加鎖的功能,其中iter是迭代器,定義了怎樣遍歷對象中的成員(包括數(shù)據(jù)成員和成員函數(shù)),filter是過濾器,定義了符合什么規(guī)則的成員才能成為一個(gè)接口,operate是執(zhí)行函數(shù),具體實(shí)施了為對象接口加鎖的功能。
而在業(yè)務(wù)類Foo的__init__函數(shù)中,只需要在最后添加一行代碼:LockerDecorator(self).decorate(),就可以完成為對象加鎖的功能。
如果你的對象提供的接口有特殊性,完全可以通過直接改寫filter或者繼承LockerDecorator并覆蓋filter的方式來實(shí)現(xiàn);此外,如果要使用其他的裝飾功能,可以寫一個(gè)繼承自DecorateClass的類,并實(shí)現(xiàn)iter,filter和operate三個(gè)函數(shù)即可。