Python裝飾器的做用是使函數包裝和方法包裝變得更容易閱讀和理解,最多見的就是@staticmethod和@classmethod,下面將從裝飾器的表現形式和經常使用裝飾器模式兩方面進行描述和總結,如有不正確之處望你們指出。
編寫自定義裝飾器有許多方法,但最簡單的方法是編寫一個函數,返回包裝原始函數調用的一個子函數緩存
#coding=utf-8 def debug(func): def wrapper(*agrs, **kwargs): '''包裝函數內部文檔''' print ("[DEBUG]:enter %s()--%s" %(func.__name__, *agrs)) return func(*agrs, **kwargs) return wrapper
@debug def say_hello(parm): ''' 提供函數文檔字符串''' print ("say_hello") if __name__ == "__main__": say_hello("Python") print ("原始函數名:%s" %(say_hello.__name__)) print ("函數文檔字符串:%s" %(say_hello.__doc__))
>>> [DEBUG]:enter say_hello()--Python
>>> say_hello
>>> 原始函數名:wrapper
>>> 函數文檔字符串:包裝函數內部文檔安全
#coding=utf-8 from functools import wraps def debug(func): @wraps(func) def wrapper(*agrs, **kwargs): '''包裝函數內部文檔''' print ("[DEBUG]:enter %s()--%s" %(func.__name__, *agrs)) return func(*agrs, **kwargs) return wrapper @debug def say_hello(parm): ''' 提供函數文檔字符串''' print ("say_hello") if __name__ == "__main__": say_hello("Python") print ("原始函數名:%s" %(say_hello.__name__)) print ("函數文檔字符串:%s" %(say_hello.__doc__))
>>> [DEBUG]:enter say_hello()--Python
>>> say_hello
>>> 原始函數名:say_hello
>>> 函數文檔字符串: 提供函數文檔字符串app
注意例1與例2的區別,也是使用裝飾器的經常使用錯誤,在使用裝飾器時不保存函數元數據(文檔字符串和原始函數名)
雖然裝飾器幾乎老是能夠用函數來實現,但若是裝飾器須要複雜的參數化或者依賴特定狀態的話,使用自定義類進行封裝可能會更好框架
#coding=utf-8 from functools import wraps class debug: def __init__(self, func): self.func = func def __call__(self, *argv, **kwargv): '''包裝函數內部文檔''' print ("[DEBUG]:enter %s()--%s" %(self.func.__name__, *argv)) self.func(*argv, **kwargv) def say_hello(something): ''' 提供函數文檔字符串 ''' print ("say_hello", something) if __name__ == "__main__": De = debug(say_hello) De("Python") print ("原始函數名:%s" %(say_hello.__name__)) print ("函數文檔字符串:%s" %(say_hello.__doc__))
>>> [DEBUG]:enter say_hello()--Python
>>> say_hello Python
>>> 原始函數名:say_hello
>>> 函數文檔字符串: 提供函數文檔字符串函數
在實際代碼中一般須要使用參數化的裝飾器,好比次數、類型判斷等,下面是一個簡單的裝飾器示例,給定重複次數,每次被調用時都會重複執行被裝飾函數ui
#coding=utf-8 from functools import wraps #參數化裝飾器 def repeat(number=3): def debug(func): @wraps(func) def wrapper(*argv, **kwargv): '''包裝函數內部文檔''' for _ in range(number): print ("[DUBEG]:enter %s()--%s" %(func.__name__, *argv)) result = func(*argv, **kwargv) return result return wrapper return debug @repeat(2) def say_hello(*agrv, **kwargv): '''提供函數文檔字符串''' print ("say_hello") if __name__ == "__main__": say_hello("Python") print ("原始函數名:%s" %(say_hello.__name__)) print ("函數文檔字符串:%s" %(say_hello.__doc__))
>>> [DUBEG]:enter say_hello()--Python
>>> say_hello
>>> [DUBEG]:enter say_hello()--Python
>>> say_hello
>>> 原始函數名:say_hello
>>> 函數文檔字符串:提供函數文檔字符串spa
和裝飾一個函數相似,也能夠寫一個函數來裝飾類,用來向類中添加功能,基本原則一致,裝飾器是一個函數或是一個可調用對象,它接受一個類做爲參數,返回一個類做爲返回值debug
#coding = utf-8 def decoratortest(cls): print ("{0.__class__.__qualname__}".format(cls)) return cls @decoratortest class testclass: def __init__(self, value): self.value = value def __repr__(self): return "{0}:88".format(self) if __name__ == "__main__": t = testclass(88)
將函數註冊到全局字典中,並將其參數和返回值保存在一個類型列表中,並對參數類型進行檢測3d
#coding=utf-8 '''將函數註冊到全局字典中,並將其參數和返回值保存在一個類型列表中''' funname = {} def parmcheck(in_= (type(None),), out_ =(type(None), )): def fun1(func): func_name = func.__name__ print ("funname:%s" %(func_name)) funname[func.__name__] = (in_, out_) def checkType(elements, types): '''用來檢查參數類型的子函數''' if len(elements) != len(types): raise TypeError("Parm count is wrong!") li = zip(elements, types) typed = enumerate(li) for index,couple in typed: argv, intype = couple if isinstance(argv, intype): print ("parm(%s) and type(%s)are all right" %(argv, intype)) continue raise TypeError("argv %d should be %s" %(argv, intype)) def decoratorfun(*argv): #types = [type(i) for i in range(len(argv))] #checkType(argv, types) checkType(argv, in_) res = func(*argv) #檢查輸出內容 if type(res) not in (tuple, list): checkable_res = (res, ) else: checkable_res = res checkType(checkable_res, out_) return decoratorfun return fun1 @parmcheck((int,int)) def meth1(a,b): print ("received:%d,%d" %(a, b)) if __name__=="__main__": meth1(1,2) print (funname)
>>> funname:meth1
>>> parm(1) and type(<class 'int'>)are all right
>>> parm(2) and type(<class 'int'>)are all right
>>> received:1,2
>>> parm(None) and type(<class 'NoneType'>)are all right
>>> {'meth1': ((<class 'int'>, <class 'int'>), (<class 'NoneType'>,))}代理
注意zip、enumerate、解包、isinstance方法的使用
緩存裝飾器與參數檢查十分類似,它的重點是關注那些內部狀態不會影響輸出的函數,每組參數均可以鏈接到惟一的結果
#coding = utf-8 import time import pickle import hashlib #全局字典 cache = {} def is_obsolete(entry, duration): print (time.time() - entry["time"]) return time.time() - entry["time"] > duration def compute_key(func, *argv, **kwargv): key = pickle.dumps((func.__name__, argv, kwargv)) return hashlib.sha1(key).hexdigest() def memoize(duration=10): def _memoize(func): def __memoize(*argv, **kwargv): key = compute_key(func,*argv, **kwargv) if ((key in cache) and not is_obsolete(cache[key], duration)): print ("we got a winner") return cache[key]['value'] result = func(*argv, **kwargv) cache[key]={"value":result,"time":time.time()} return result return __memoize return _memoize @memoize(3) def fun(a,b): print (a+b) return a+b if __name__=="__main__": fun(2,3) fun(2,2) fun(2,3) print (cache)
>>> 5
>>> 4
>>> 0.0
>>> we got a winner
>>> {'a99634a4e619a2ad129df1b51002a8c0cb9cca2b': {'value': 5, 'time': 1518243058.456
>>> 425}, '99683ddc4e22fd3f37e473de5d61699a5c27c2c6': {'value': 4, 'time': 151824305
>>> 8.456425}}
代理裝飾器使用全局機制來標記和註冊函數。好比一個根據當前用戶來保護代碼訪問的安全層可使用集中式檢查器和相關的可調用對象要求的權限來訪問,這一模型經常使用於Python Web框架中,用於定義法布類的安全性
上下文裝飾器確保函數能夠容許在正確的上下文中,好比臨界資源的使用
#coding=utf-8 from threading import RLock lock = RLock() def synchronized(func): def _synchronized(*argv, **kdargv): lock.require() try: return func(*argv, **kdargv) except: print ("fun error!!!") finally: lock.release() return _synchronized
上下文裝飾器一般會被上下文管理器with語句代替