python的裝飾器使用是python語言一個非常重要的部分,裝飾器是程式設計模式中裝飾模式的具體化,python提供了特殊的文法糖可以非常方便的實現裝飾模式。
裝飾模式簡介
- 什麼叫裝飾?簡單來說就是在已有的對象上添加格外的屬性、方法或一段代碼實現一個新的功能但是又不改變原來的對象或內容相關的結構;這樣做的意義在於為了是程式的設計符合開放-封閉原則,即對擴充開放,但是對修改封閉;也就是說一個類或方法被定義完成後就不要再去修改它了,你可以通過繼承、裝飾、代理等一些模式去擴充它的功能;最終的目的是為了降耦合和保證系統的穩定性。
python裝飾模式的簡單實現
class Car(object): """ 一個汽車類 """ def __init__(self): self.logo = "平治" # 車標 self.oil = 2 # 油耗 self.ornamental = None # 裝飾品 # 安裝空調 def air_conditioner(self): print("空調真舒服!") def decorator(self, component): """用來額外添加裝飾方法的""" self.ornamental = component# 由於汽車的裝飾品會發生變化class Cushion(object): """ 坐墊 """ def __init__(self): self.name = "席夢思"class Flower(object): """ 裝飾花 """ def __init__(self, name): self.name = nameif __name__ == '__main__': car = Car() cushion = Cushion() flower = Flower("玫瑰") # 汽車添加一個坐墊 car.decorator(cushion) print(car.ornamental) # 添加一個花 car.decorator(flower) print(car.ornamental)
上例中坐墊和花是可以為汽車動態添加的額外的屬性,這樣的話可以精簡Car類的結構,同時可以擴充Car類的功能。
python的裝飾器
- python裝飾器主要針對的是為一段已完成的方法增加額外的需要執行的代碼,為了保持原來的方法不變,裝飾器裝飾後應該返回一個新的方法;實現這種功能需要一個載體,這個載體可以是函數也可以是類,同時python提供了文法糖@來完成裝飾功能。
python裝飾器實現原理介紹
@decoratordef get_name(): pass
如上所示,當代碼初始化載入內容相關的時候,先定義get_name函數,decorator一定要在定義get_name函數之前載入到上下文中,解譯器遇到@這種文法糖,會將@下的get_name函數作為參數代入decorator中執行decorator並返回新的方法,重新將返回的方法賦值給get_name。
那decorator究竟是什麼結構呢?我們可以看裝飾器的要求,需要返回一個新的方法,因此可以是閉包結構,也可以是類結構。
使用閉包結構的裝飾器
def decorator(func): def new_func(*args,**kwargs): "do something" res = func(*args,**kwargs) "do something" return res return new_func@decoratordef get_name(): pass
什麼是閉包?在函數中定義一個新的函數,這個函數用到了外邊函數的變數,將這個函數以及用到的一些變數稱之為閉包。閉包函數在初始化內容相關的時候是不會被定義的,只有在執行的時候才會被定義。
上例所示,get_name函數作為參數傳遞到decorator函數中執行decorator函數返回new_func,new_func函數的參數就是get_name函數的參數,new_func賦值給get_name。此時get_name方法中添加了額外的代碼。
如果需要在原來的裝飾器上還需要添加額外的參數,那就必須使用雙層閉包結構了。
def new_decorator(pas=""): def decorator(func): def new_func(*args,**kwargs): "do something" print(pas) res = func(*args,**kwargs) "do something" return res return new_func return decorator@new_decorator('new prams')def get_name(): pass
如上所示,pas參數會被傳遞到閉包函數中去,此時載入上下文時,new_decorator由於內建了()形式,會被直接執行,返回內層decorator函數,然後按照正常的方式裝飾過程執行,pas參數由於被內層函數引用住,會長久地駐留在記憶體中而不會被釋放。
如果在已被裝飾的函數上再添加額外的代碼功能,就需要再添加裝飾器了,此時的重點就在於裝飾器函數的執行順序了。
def new_decorator(pas=""): def decorator(func): def new_func(*args,**kwargs): "do something" print(pas) res = func(*args,**kwargs) "do something" return res return new_func return decoratordef tow_decorator(func): def new_func(*args, **kwargs): res = func(*args, **kwargs) "do other something" return res return new_func@tow_decorator@new_decorator(pas='hhhhh')def get_name():
裝飾器函數的執行順序不同於我們的習慣,其按就近原則,即如上new_decorator會先執行裝飾,將返回的函數賦值給get_name,然後依次往上;因此裝飾代碼的整體結構為tow_decorator添加的額外程式碼封裝裹new_decorator的代碼,這在需要考慮代碼的執行順序是很重要。
使用類結構的裝飾器
class Decorator(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): """ 添加額外的代碼 :param args: :param kwargs: :return: """ print('do something') return self.func(*args, **kwargs)@Decoratordef get_name(): passprint(isinstance(get_name, Decorator))# 結果True
如上,由於在python中,一個對象只要具備了__call__方法,就可以使用xxx()的形式進行調用執行call方法中的代碼,利用的這個原理我們就可以實現裝飾器的結構;
初始化上下文時,執行Decorator,get_name作為參數對Decorator的執行個體進行初始化,返回一個Decorator的執行個體賦值給get_name,此時get_name是一個類的執行個體對象,當調用get_name時會執行Decorator執行個體對象的call方法。但這種方式相對於閉包結構來說就有點複雜了,一般不用。
如果使用類結構需要添加額外的參數怎麼辦呢?和閉包結構一樣,再添加一層。
def three_decorator(pas=''): class Decorator(object): def __init__(self, func): self.func = func self.pas = pas def __call__(self, *args, **kwargs): """ 添加額外的代碼 :param args: :param kwargs: :return: """ print('do something') return self.func(*args, **kwargs) return Decorator@three_decorator('hhhhh')def get_name(): pass
使用裝飾器的副作用
裝飾器的用處不用多說,函數校正、進入日誌、函數執行前後處理等多情境都需要用到,它也有一點副作用。
def tow_decorator(func): def new_func(*args, **kwargs): """new_func function""" res = func(*args, **kwargs) print("do other something") return res return new_func@tow_decoratordef get_name(): """get_name function""" print('jjjjjj')if __name__ == '__main__': import inspect print(inspect.signature(get_name)) x = inspect.getsource(get_name) print(x) print(get_name.__doc__)# 結果(*args, **kwargs) # 函數簽名 def new_func(*args, **kwargs): # 函數源碼 """new_func function""" res = func(*args, **kwargs) print("do other something") return res‘new_func function’ # 函數文檔
可以看到,由於get_name被裝飾後指向的是new_func函數,所以擷取的資訊不再是get_name函數的說明了,對於調試是不方便的。我們可以使用functools模組的wraps函數基本消除這個副作用。
def tow_decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): """new_func function""" res = func(*args, **kwargs) print("do other something") return res return new_func@tow_decoratordef get_name(): """get_name function""" print('jjjjjj')if __name__ == '__main__': print(get_name.__doc__)# 結果()@tow_decoratordef get_name(): """get_name function""" print('jjjjjj')get_name function
建立裝飾器的一般方式
根據我們上面的分析,標準的裝飾器結構:
import functoolsdef decorator(func): @functools.wraps(func) def new_func(*args,**kwargs): "do something" res = func(*args,**kwargs) print("do something") return res return new_func
- 使用decorator.py模組的decorator和decorate
閉包結構的裝飾函數不太直觀,我們可以使用第三方包類改進這樣的情況,讓裝飾器函數可讀性更好。
from decorator import decorator, decorate, contextmanagerdef wrapper(func, *args, **kwargs): """裝飾函數的內容""" res = func(*args, **kwargs) print("do other something") return resdef one_decorator(func): """進行裝飾""" return decorate(func, wrapper)@one_decoratordef get_name(pas='fff'): """get_name function""" print('jjjjjj')if __name__ == '__main__': import inspect print(inspect.signature(get_name)) x = inspect.getsource(get_name) print(x) print(get_name.__doc__) get_name()
將新函數單獨寫出來,使得原來的閉包結構變成了兩個函數,代碼可讀性變好了,可以發現decorate幫我們處理了裝飾器的副作用,但是一個裝飾器需要定義兩個函數有點麻煩,要更優雅使用decorator。
from decorator import decorator, decorate, contextmanager@decoratordef five_decorator(func, *args, **kwargs): res = func(*args, **kwargs) print("do other something") return res@five_decoratordef get_name(pas='fff'): """get_name function""" print('jjjjjj')if __name__ == '__main__': import inspect print(inspect.signature(get_name)) x = inspect.getsource(get_name) print(x) print(get_name.__doc__) get_name()
現在裝飾器變成定義一個函數的模樣了,簡單又直觀,同時也處理了副作用,是一種理想的裝飾器建立方式。
import wrapt@wrapt.decoratordef six_decorator(func, instance, args, kwargs): res = func(*args, **kwargs) print(instance) print("do other something") return res@six_decoratordef get_name(pas='fff'): """get_name function""" print('jjjjjj')if __name__ == '__main__': import inspect print(inspect.signature(get_name)) x = inspect.getsource(get_name) print(x) print(get_name.__doc__) get_name()
使用wrapt模組的decorator也可以很直觀地實現裝飾器,但是該裝飾器的參數func, instance, args, kwargs是固定的;args, kwargs參數前面沒有*號,instance在裝飾器裝飾類方法的時候可以得到該類的執行個體對象;func是被裝飾的函數。
- 天宇之遊
- 出處:http://www.cnblogs.com/cwp-bg/
- 本文著作權歸作者和部落格園共有,歡迎、交流,但未經作者同意必須保留此段聲明,且在文章明顯位置給出原文連結。