問題:給函數加一個外包裝層,已添加額外的處理,例如,記錄日誌,計時統計等 解決方案:能夠定義一個裝飾器來包裝函數
問題:當一個函數被裝飾器裝飾時,一些重要的元數據好比:函數名、文檔字符串、函數註解以及調用簽名都丟失了 解決方案:每當定義一個裝飾器時應該老是記得爲底層的包裝函數添加functools庫中的@wraps裝飾器
#問題:當一個函數被裝飾器裝飾時,一些重要的元數據好比:函數名、文檔字符串、函數註解以及調用簽名都丟失了 #解決方案:每當定義一個裝飾器時應該老是記得爲底層的包裝函數添加functools庫中的@wraps裝飾器 import time import functools def timethis(func): @functools.wraps(func) def wrapper(*args,**kwargs): start_time = time.time() result = func(*args,**kwargs) end_time = time.time() print(func.__name__,end_time-start_time) return result return wrapper @timethis def mysleep(num:int): """ 原函數註釋文檔 :param num: :return: """ time.sleep(num) print("我是原函數") mysleep(3) print(mysleep.__name__) print(mysleep.__doc__) print(mysleep.__annotations__) #若是裝飾器使用@functools.wraps(func) 裝飾,咱們就可使用下面的方法獲取到原函數!!! mysleep.__wrapped__(3)
問題: 咱們已經把裝飾器添加到函數上了,可是想撤銷它,訪問未經包裝的原函數。 解決方案:假設裝飾器已經實現了@warps(func),通常來講咱們能夠經過訪問__wrapped__屬性來獲取到原函數
問題:咱們想編寫一個可接收參數的裝飾器函數 解決方案:假設咱們想編寫一個爲函數添加日誌功能的裝飾器,可是又容許用戶指定日誌的等級以及一些其餘的細節操做做爲參數。
import logging import functools def logged(level,name=None,message=None): def decorate(func): logname = name if name else func.__module__ log = logging.getLogger(logname) logmsg = message if message else func.__name__ @functools.wraps(func) def wrapper(*args,**kwargs): log.log(level,message) return func(*args,**kwargs) return wrapper return decorate
問題:咱們想編寫一個裝飾器來包裝函數,可是可讓用戶調整裝飾器的屬性,這樣在運行時就可以控制裝飾器的行爲
from functools import wraps,partial import logging def attach_wrapper(obj,func=None): if func is None: return partial(attach_wrapper,obj) setattr(obj,func.__name__,func) return func def logged(level,name=None,message=None): def decorate(func): logname = name if name else func.__module__ log = logging.getLogger(logname) logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args,**kwargs): log.log(level,logmsg) return func(*args,**kwargs) @attach_wrapper(wrapper) def set_level(newlevel): nonlocal level level = newlevel @attach_wrapper(wrapper) def set_message(newmsg): nonlocal logmsg logmsg = newmsg return wrapper return decorate logging.basicConfig(level=logging.DEBUG) @logged(logging.DEBUG) def add(x,y): return x + y add(2,5) add.set_message("Add called") add(3,8)
問題:咱們想編寫一個單獨的裝飾器,使其既能夠像@decorator 這樣不帶參數,也能夠像@decorator(x,y,z)這樣接收可選參數
from functools import wraps,partial import logging def logged(func=None,*,level=logging.DEBUG,name=None,message=None): if func is None: return partial(logged,level=level,name=name,message=message) logname = name if name else func.__module__ log = logging.getLogger(logname) logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args,**kwargs): log.log(level,logmsg) return func(*args,**kwargs) return wrapper @logged def add(x,y): logging.debug("hahahah") return x+y #沒有參數時,裝飾器就至關於:logged(func),因此裝飾器的第一個參數就是func,其餘都是可選參數 @logged(level=logging.CRITICAL,name="example") def spam(): print("spam!!!") #有參數時,裝飾器就至關於logged(level=logging.DEBUG,name="example")(spam) #巧妙的利用functools.partial 將構建好的方法返回 add(1,2) spam()
問題:咱們想爲函數參數添增強制類型檢查功能,將其做爲一種斷言或者與調用者之間的契約
from inspect import signature from functools import wraps def typeassert(*ty_args,**ty_kwargs): def decorate(func): if not __debug__: return func sig = signature(func)#獲取func的參數簽名(x,y,z) bound_types = sig.bind_partial(*ty_args,**ty_kwargs).arguments# 參數簽名與類型參數作映射 [("x",<class "int">),("z",<class "int">)] @wraps(func) def wrapper(*args,**kwargs): bound_values = sig.bind(*args,**kwargs).arguments# 參數簽名與函數參數作映射 for name,value in bound_values.items(): if name in bound_types:#判斷參數是否有類型限制 if not isinstance(value,bound_types[name]): raise TypeError("Argument {} must be {}".format(name,bound_types[name])) return func(*args,**kwargs) return wrapper return decorate class A(): def a(self): print("a") @typeassert(int,A,z=int) def add(x,y,z): print(x,y,z) return x add(1,A(),3) #想法:參數類型的限制可使用在參數處理方法中,對前端接收的參數進行檢查,也可使用在一些須要限制傳入參數類型的地方 #注:此裝飾器一個微妙的地方,只檢查傳遞的參數,若是是默認參數,沒有進行傳遞,參數類型不進行檢查 @typeassert(int,list) def bar(x,items=None): if items is None: items = [] items.append(x) return items print(bar(2))
問題: 咱們想在類中定義一個裝飾器,並將其做用到其餘函數或方法上
from functools import wraps class A: def decorator1(self,func): @wraps(func) def wrapper(*args,**kwargs): print("decorator 1") return func(*args,**kwargs) return wrapper @classmethod def decorator2(cls,func): @wraps(func) def wrapper(*args,**kwargs): print("decorator 2") return func(*args,**kwargs) return wrapper #思考:@property 其實是一個擁有 getter(),setter(),deleter()方法的類,每個方法均可做爲一個裝飾器 #幾個裝飾器均可以操縱實例的狀態,所以,若是須要裝飾器在背後記錄或合併信息,這是一個很明智的方法。
問題: 咱們想用裝飾器來包裝函數,可是但願獲得的結果是一個可調用的實例。咱們須要裝飾器既能在類中工做,也能夠在類外部使用 解決方案:要把裝飾器定義成類實例,須要確保在類中實現__call__()和__get__()方法
import types from functools import wraps class Profield: def __init__(self,func): wraps(func)(self) self.ncalls = 0 def __call__(self, *args, **kwargs): self.ncalls +=1 return self.__wrapped__(*args,**kwargs) def __get__(self, instance, cls): if instance is None: return self else: return types.MethodType(self,instance) #該裝飾器至關於爲函數添加一個屬性 ncalls
問題:咱們想在類或者靜態方法上應用裝飾器 解決方案:將裝飾器做用到類和靜態方法上是簡單而直接的,可是要保證裝飾器在應用的時候須要放在@classmethod 和 @staticmethod 以前,示例以下:
import time from functools import wraps def timethis(func): @wraps(func) def wrapper(*args,**kwargs): start = time.time() r = func(*args,**kwargs) end = time.time() print(end-start) return r return wrapper
@classmethod 和 @staticmethod 裝飾器並不會返回一個可執行對象,因此裝飾器都要放在他們下面!!!
問題:咱們想編寫一個裝飾器,爲被包裝的函數添加額外的參數,可是添加的參數不能影響到該函數已有的調用約定 解決方案:
from functools import wraps def optinoal_debug(func): @wraps(func) def wrapper(*args,debug=False,**kwargs): if debug: print("Calling",func.__name__) return func(*args,**kwargs) return wrapper
函數中的一部分參數被裝飾器解析所用,剩下參數給到函數,能夠用被包裝函數的參數來控制裝飾器的行爲
#問題:咱們想檢查或改寫一部分類的定義,以此來修改類的行爲,可是不想經過繼承或者元類的方式來作 #解決方案:
def log_getattribute(cls): orig_getattribute = cls.__getattribute__ def new_getattribute(self,name): print("getting",name) return orig_getattribute(self,name) cls.__getattribute__ = new_getattribute return cls @log_getattribute class A: def __init__(self,x): self.x = x def spam(self): pass a = A(42) a.x
能夠經過此方法對類的屬性作監控