在Python裏面,函數能夠做爲參數傳入一個函數,函數也能夠複製給變量,經過變量調用函數。裝飾器能夠擴展一個函數的功能,爲函數作一個裝飾器註解,能夠把裝飾器裏面定義的功能於全部函數提早執行,提高代碼的複用程度。java
如今有這麼個場景。python
互聯網公司裏面有各類員工,程序員,前臺...,程序員在打開電腦前,須要打卡,前臺要早點來開門(我也不清楚,誰開門,這裏假定,前臺開門),前臺開門前也須要打卡。也就是說,打卡是全部員工的最早的公共動做,那麼能夠把打卡這個功能抽出來做爲公共邏輯。程序員
天然想到,能夠實現以下。數組
def di(f): print('%s 打卡,滴...' % f.__name__) return f() def boot(): print('開機') def open(): print('開門') if __name__ == '__main__': """ 程序員開機以前,前臺開門以前,都須要先在門外指紋機打卡。 """ di(boot) di(open)
定義了一個函數di(f)
,能夠打印f.__name__
即f
的函數名信息,同時返回f()的執行結果。app
注意:__name__
若是做爲模塊導入,module.__name__
就是模塊本身的名字,若是模塊本身做爲腳本執行,返回__main__
。函數
執行結果:設計
boot 打卡,滴... 開機 open 打卡,滴... 開門
這樣設計,若是有不少函數都要調用,就很麻煩,那麼裝飾器就排上了用場。日誌
裝飾器:在代碼運行期間動態增長功能的方式,稱之爲「裝飾器」(Decorator)。code
定義一個di(f)
方法,仍是把要執行的邏輯的函數做爲參數傳入,裏面定義一個wrapper
函數,返回值是f
的執行結果。
在if __name__ == '__main__':
裏面,調用了這個裝飾器,不修改定義好了的函數,在運行期間動態添加功能"打卡"。it
import functools # 簡單裝飾器 def di(f): """ 程序員開機以前,前臺開門以前,都須要先在門外指紋機打卡。 :param f: 傳入一個函數 :return: """ # 把原始函數的__name__等屬性複製到wrapper() @functools.wraps(f) def wrapper(): print('%s 打卡,滴...' % f.__name__) return f() return wrapper def boot(): print('開機') def open(): print('開門') if __name__ == '__main__': # 第一種,簡單裝飾器 a = di(boot) a1 = di(open) print(a.__name__) # 結果wrapper 加@functools.wraps(f)後結果爲 boot a() a1()
di(boot)
的返回值a就是wrapper
函數,經過a()
就調用了wrapper
函數,獲得boot
的返回值。同理,di(open)
同樣。
結果
boot boot 打卡,滴... 開機 open 打卡,滴... 開門
因爲di(boot)
的返回值a
就是wrapper
函數,那麼print(a.__name__)
的結果就理所固然是是wrapper
,咱們但願是boot
,怎麼辦,functools.wraps(f)
這個註解能夠把原始函數boot
的__name__
等屬性複製到wrapper()
,把這行代碼註釋也能運行,那麼print(a.__name__)
的結果就是wrapper
。
經過@
語法糖,也能將裝飾器應用於函數上面,推薦。
import functools def di(f): """ 程序員開機以前,前臺開門以前,都須要先在門外指紋機打卡。 :param f: 傳入一個函數 :return: """ # 把原始函數的__name__等屬性複製到wrapper() @functools.wraps(f) def wrapper(): print('%s 打卡,滴...' % f.__name__) return f() return wrapper # @ 語法糖 @di def boot2(): print('開機') @di def open2(): print('開門') if __name__ == '__main__': # 第二種,@ 語法糖 boot2() open2()
@di
標記至關於,a2 = di(boot2) a2()
。不用這麼麻煩,由於加了@
符號標記,直接用boot2()
調用裝飾器便可。
結果
boot2 打卡,滴... 開機 open2 打卡,滴... 開門
業務邏輯函數可能須要參數,好比:
def boot(name): print('%s 開機' % name)
那麼,只須要將前面的裝飾器修改成:
import functools # 業務邏輯函數須要參數 def di(f): """ 程序員開機以前,前臺開門以前,都須要先在門外指紋機打卡。 :param f: 傳入一個函數 :return: """ # 把原始函數的__name__等屬性複製到wrapper() @functools.wraps(f) def wrapper(*args, **kwargs): print('%s 打卡,滴...' % f.__name__) return f(*args, **kwargs) return wrapper @di def boot(name): print('%s 開機' % name) if __name__ == '__main__': boot('keguang')
結果:
boot 打卡,滴... keguang 開機
給wrapper
也加上*args, **kwargs
參數,在boot
裏面直接調用f(*args, **kwargs)
便可。順便提一下:
*args
:能夠傳入一個數組參數**kwargs
:能夠傳入一個k-v
對參數前後順序對應,數組參數在前。舉例:
def f(*args, **kwargs): print('args=', args) print('kwargs=', kwargs) print(f(1, 2, 3, a = 'a', b = 'b')) # 結果 # args= (1, 2, 3) # kwargs= {'a': 'a', 'b': 'b'}
若是裝飾器也帶參數,好比如今若是某個員工早晨上班來得早< 9:00
,咱能夠作個表揚,那麼至關於只須要在前面的di()
外面套一層函數,di_args
便可,在wrapper
裏面。使用這個參數
import functools # 帶參數的裝飾器 def di_args(time): def di(f): """ 程序員開機以前,前臺開門以前,都須要先在門外指紋機打卡。 :param f: 傳入一個函數 :return: """ # 把原始函數的__name__等屬性複製到wrapper() @functools.wraps(f) def wrapper(*args, **kwargs): if time < '9:00': print('來的真早,很棒。。。') print('%s 打卡,滴...' % f.__name__) return f(*args, **kwargs) return wrapper return di @di_args('8:00') def boot(name): print('%s 開機' % name) if __name__ == '__main__': boot('keguang')
參數在@di_args('8:00')
傳入便可,有點像java
裏面的註解。最後仍是經過boot('keguang')
調用便可,結果:
來的真早,很棒。。。 boot 打卡,滴... keguang 開機
類裝飾器主要依靠類的__call__
方法,當使用 @
形式將裝飾器附加到函數上時,就會調用此方法。
# 類裝飾器 class di(object): def __init__(self, f): self._f = f def __call__(self, *args, **kwargs): print('decorator start...') self._f() print('decorator end...') @di def boot(): print('開機') if __name__ == '__main__': boot()
加上@di
裝飾器標識,會用boot
去實例化di
類,而後執行__call__
函數,object
表示這個類能夠傳入任何類型參數。
運行結果
decorator start... 開機 decorator end...
裝飾器有一個典型的應用場景就是打log
日誌,若是全部邏輯都須要日誌記錄程序的運行情況,那麼能夠對這些邏輯(函數)加日誌模塊裝飾器,就能達到相應目的。