先給出一個四人團對Decorator mode的定義:動態地給一個對象添加一些額外的職責。
再來說說這個模式的好處:認證,許可權檢查,記日誌,檢查參數,加鎖,等等等等,這些功能和系統業務無關,但又是系統所必須的,說的更明白一點,就是面向方面的編程(AOP)。AOP把與業務無關的代碼十分乾淨的從系統中切割出來,但是Decorator mode的強大遠不止於此,本文的重點在於Decorator mode在Python中的應用,所以就不再過多描述Decorator mode本身了,要想深入瞭解該模式,請參考四人團的經典之作《設計模式》。
在Python中Decorator mode可以按照像其它程式設計語言如C++, Java等的樣子來實現,但是Python在應用裝飾概念方面的能力上遠不止於此,Python提供了一個文法和一個編程特性來加強這方面的功能。Python提供的文法就是裝飾器文法(decorator),如下:
@aoo
def foo(): pass
def aoo(fn):
return fn
這裡不對裝飾器文法做過多的解釋,因為裝飾器文法也是基於我將要介紹的另一個編程特性,當我介紹完另一個編程特性後,相信你會對裝飾器文法有更深入的認識。
一個十分重要的編程特性“閉包”(closure)隆重登場(題外話:據說“閉包”已經進入java下一版的特性候選列表了)
在Python,PHP,Perl,Ruby,JavaScript等動態語言中,都已經實現了閉包特性,為什麼這個特性那麼重要呢?我們先來看看它的通俗一些的定義:
OO編程範式中的對象是“整合了函數的資料對象”,那麼閉包就是“整合了資料的函數對象”
借用一個非常好的說法來做個總結:對象是附有行為的資料,而閉包是附有資料的行為。
下面左邊的foo1隻是一個普通的內嵌函數,而右邊的boo則是一個閉包,
def foo(x): y = x def foo1 (): a = 1 return a return foo1 |
def aoo(a, b): c = a def boo (x): x = b + 1 return x return boo |
|
boo的特殊性在於引用了外部變數b,當aoo返回後,只要傳回值(boo)一直存在,則對b的引用就會一直存在。
上面的知識可能需要花些時間消化,如果你覺得已經掌握了這些知識,下面就迴歸正題,看看這些語言特性是怎樣來實現Python中裝飾的概念的。
還是讓我們先看一個簡單的例子,然後逐步深入。這個例子就是加鎖,怎樣實現加鎖的功能?
具體需求是這樣的:我有一個對象,實現了某些功能並提供了一些介面供其它模組調用,這個對象是運行在並發的環境中的,因此我需要對介面的調用進行同步,第一版的代碼如下:
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()
這版代碼的問題很明顯,那就是每個介面函數都有相同的加鎖/解鎖代碼,重複的代碼帶來的是更多的鍵入,更多的閱讀,更多的維護,以及更多的修改,最主要的是,程式員本應集中在業務上的的精力被分散了,而且請注意,真正的業務代碼在距離函數定義2次縮排處開始,即使你的顯示器是寬屏,這也會帶來一些閱讀上的困難。
你直覺的認為,可以把這些代碼收進一個函數中,以達到複用的目的,但是請注意,這些代碼不是一個完整同一的代碼塊,而是在中間嵌入了業務代碼。
現在我們用裝飾器文法來改進這部分代碼,得到第2版代碼:
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
一個裝飾器函數的第一個參數是所要裝飾的那個函數對象,而且裝飾器函數必須返回一個函數對象。如sync函數,當其裝飾interface1時,參數func的值就是interface1,傳回值是wrapper,但類Foo執行個體的interface1被調用時,實際調用的是wrapper函數,在wrapper函數體中間接調用實際的interface1;當interface2被調用時,也調用的是wrapper函數,不過由於在裝飾時func已經變成interface2,所以會間接地調用到實際的interface2函數。
使用裝飾器文法的好處:
- 代碼量大大的減少了,更少的代碼意味著更少的維護,更少的閱讀,更少的鍵入,好處不一而足(可複用,可維護)
- 使用者基本上將絕大部分精力放在了業務代碼上,而且少了加減鎖的代碼,可讀性也提高了
缺點:
- 業務對象Foo中有一個非業務資料成員lock,很礙眼;
- 相當程度的耦合,wrapper的第一個參數必須是對象本身,而且被裝飾的對象中必須有一個lock對象存在,這給客戶對象添加了限制,使用起來不是很舒服。
我們可以更進一步想一想:
- lock對象必須要放在Foo中嗎?
- 為每個介面函數都鍵入@sync還是很煩人的重複性人工工作,如果漏添加一個,還是會造成莫名其妙的執行階段錯誤,為什麼不集中處理呢?
為瞭解決上述的缺點,第3版代碼如下:
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
對對象的功能裝飾是一個更一般的功能,不僅限於為介面加鎖,我用2個類來完成這一功能,DecorateClass是一個基類,只定義了遍曆並應用裝飾功能的演算法代碼(template method),LockerDecorator實現了為對象加鎖的功能,其中iter是迭代器,定義了怎樣遍曆對象中的成員(包括資料成員和成員函數),filter是過濾器,定義了符合什麼規則的成員才能成為一個介面,operate是執行函數,具體實施了為對象介面加鎖的功能。
而在業務類Foo的__init__函數中,只需要在最後添加一行代碼:LockerDecorator(self).decorate(),就可以完成為對象加鎖的功能。
如果你的對象提供的介面有特殊性,完全可以通過直接改寫filter或者繼承LockerDecorator並覆蓋filter的方式來實現;此外,如果要使用其他的裝飾功能,可以寫一個繼承自DecorateClass的類,並實現iter,filter和operate三個函數即可。
轉載自:Python和Decorator(裝飾器)模式