python裝飾器的使用
1. 裝飾者模式
裝飾者模式是常用的軟體設計模式之一。通過此設計模式,我們能夠在不修改任何底層程式碼情況下,給已有物件賦予新的職責。python中可以用裝飾器簡單地實現裝飾者模式。
1.1 將函式作為引數傳遞
在C/C++ 中,函式指標可以將函式作為引數傳遞給另一函式。而在python 中,函式也是物件的一種,函式可以被引用,也可直接作為引數傳入函式,以及作為容器物件的元素。python中可以採用如下方法實現裝飾者模式:
#!/usr/bin/env python3.6 # -*- coding: utf-8 -*- def add(x, y): result = x+y return result def log(func): def wrapper(*args, **kwargs): result = func(*args) print(func.__name__,'has been called\n') return result return wrapper if __name__ == '__main__': print(log(add)(1,2))
上述程式碼中,log
函式以需要被裝飾的函式作為引數,並返回函式物件。被返回的函式的引數為可變引數*args
與**kwargs
(*args
引數會被封裝成tuple
,**kwargs
引數則會被封裝成字典物件),以適應不同函式的不同引數,保證通用性。
1.2 裝飾器
上面的實現方法有些繁雜,所有呼叫被裝飾的函式之處的程式碼,都要進行相應修改,自然不符合python簡潔易讀的特性。因此python中給出相應語法糖來增加可讀性和易用性,那便是“裝飾器”。
#!/usr/bin/env python3.6 # -*- coding: utf-8 -*- from functools import wraps def log(func): #@wraps(func) def wrapper(*args, **kwargs): result = func(*args) print(func.__name__,'has been called') return result return wrapper #等價於add = log(add) @log def add(x, y): result = x+y return result if __name__ == '__main__': print(add(1,2)) print(add.__name__)
執行情況如下:
>>print(add(1,2)) add has been called 3 >>print(add.__name__) wrapper
但上述方法亦有缺陷,原函式add的元資料(比如名字、文件字串、註解和引數簽名)會丟失。為避免缺陷,任何時候你定義裝飾器的時候,都應該使用functools
庫中的@wraps
裝飾器來註解底層包裝函式(程式碼中註釋部分)。@wraps
有一個重要特徵是它能讓你通過屬性__wrapped__
直接訪問被包裝函式。
改進後執行情況 :
>>print(add(1,2)) add has been called 3 >>print(add.__name__) add
1.3 解除裝飾器
當裝飾器已經作用於某函式,而你想撤銷它,那麼可以訪問__wrapped__
屬性來訪問原始函式
orig_add = add.__wrapped__ orig_add(1,2)
但若使用了多個裝飾器,__wrapped__
屬性會變得不可控,應儘量避免使用。
若有如下程式碼:
#!/usr/bin/env python3.6 # -*- coding: utf-8 -*- import functools import time def metric(func): @functools.wraps(func) def wrapper(*args,**kv): print('Decorator1') f = func(*args,**kv) return f return wrapper def logging(func): @functools.wraps(func) def wrapper(*args,**kv): print('Decorator2') f = func(*args,**kv) return f return wrapper @metric @logging def normalize(name): sName = name[0:1].upper() + name[1:].lower() print(sName) if __name__ == '__main__': normalize('heLlO') normalize.__wrapper__('')
執行情況如下:
>>normalize('helLo') Decorator1 Decorator2 Hello >>normalize.__wrapped__('world') Decorator2 World
1.4 定義帶引數的裝飾器
from functools import wraps def log(text): def decorator(func): @wraps(func) def wrappering(*args,**kv): print('%s %s():'%(text,func.__name__)) return func(*args,**kv) return wrappering return decorator @log('run') def normalize(name): sName = name[0:1].upper() + name[1:].lower() print(sName)
裝飾器函式可以帶引數,最外層的函式會將引數傳給內層的裝飾器函式,即wrappering
函式是可以使用log
的傳入引數的。
裝飾器處理過程與下面是等價的:
normalize = log('run')(normalize)
參考文獻
[1].David Beazley, Brian K. Jones. Python Cookbook 3rd Edition
[2]. Wesley Chun. Core Python Applications Programming 2nd Edition