函數是個對象,並且可以賦值給一個變量,通過變量也能調用該函數:
>>> def now(): ... print('2017-12-28') ... >>> l = now >>> l() 2017-12-28
利用函數的_name_屬性,可以拿到函數的名字:
>>> now.__name__ 'now' >>> l.__name__ 'now'
如果我們在調用函數now()前后自動打印日志,但又不允許修改now()函數的定義——在代碼運行期間動態增加功能的方式,稱之為‘裝飾器’Decorator。
比如,定義一個能打印日志的decorator:
>>> def log(func): ... def wrapper(*args,**kw): ... print('call %s():' % func.__name__) ... return func(*args,**kw) ... return wrapper ...
觀察log 函數,發現,本質上這就是一個返回函數的高階函數!log作為一個decorator,接收一個函數作為參數,冰飯一個函數。借助python的@語法,把decorator置於函數的定義的地方:
>>> @log ... def now(): ... print('2017-12-28') ... >>> now() call now(): 2017-12-28
在調用now()函數時候,不僅運行了now函數,還會在此之前打印一行日志。
其實,把@log放到now()函數的定義前,相當於執行了:
now = log(now)
log是一個decorator,返回一個函數,返回的這個函數名字叫wrapper,原來的now()函數還存在,這個時候now變量指向了這個返回函數wrapper。當調用now()將執行新的函數wrapper函數。wrapper函數的參數是(*args,**kw),因此wrapper()函數可以接受任意參數!在wrapper函數內部,首先打印日志,再緊接着調用原始函數。
分割線-------------------------------
如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數。比如要自定義log的文本,定義、用法和結果:
>>> def log(text): ... def decorator(func): ... def wrapper(*args,**kw): ... print('%s %s():' %(text,func.__name__)) ... return func(*args,**kw) ... return wrapper ... return decorator ... >>> @log('執行') ... def now(): ... print('2017-12-28') ... >>> now() 執行 now(): 2017-12-28
前面的例子中包含了兩層def嵌套,后面的例子中包含了三層def嵌套。其實,三層嵌套的效果類似:
now = log('執行')(now)
解析:首先執行log('執行'),返回的是decorator函數,再調用返回函數,參數是now函數,最終的返回值是wrapper函數。
但是,我們執行下面語句來測試:
>>> now.__name__ 'wrapper'
我們發現:經過decorator裝飾后的函數,他們的__name__屬性已經從now變成了wrapper。這是因為返回的那個函數wrapper函數名字就是wrapper,所以,需要把原始函數的__name__屬性復制到wrapper函數中,否則,其他一些依賴函數簽名的代碼執行就會報錯。
實際上我們並不需要編寫wrapper.__name__ = func.__name__這樣的代碼,python內置了functools.wraps就是為了這個。最
最后一步,一個完整的decorator的寫法如下:
>>> import functools >>> def log(func): ... @functools.wraps(func) ... def wrapper(*args,**kw): ... print('執行 %s()' %func.__name__) ... return func(*args,**kw) ... return wrapper ... >>> @log ... def now(): ... print('日志') ... >>> now <function now at 0x03317198> >>> now() 執行 now() 日志
針對帶有參數的decorator:
>>> import functools >>> def log(text): ... def decorator(func): ... @functools.wraps(func) ... def wrapper(*args,**kw): ... print('%s %s()' %(text,func.__name__)) ... return func(*args,**kw) ... return wrapper ... return decorator ... >>> @log('ABC') ... def now(): ... print('這么復雜干嘛') ... >>> now() ABC now() 這么復雜干嘛
例子:設計一個decorator,可作用於任何函數上,並打印該函數的執行時間:
>>> import time,functools >>> def log(func): ... @functools.wraps(func) ... def wrapper(*args,**kw): ... t1 = time.time() ... r = func(*args,**kw) ... print('%s excute in %s ms'%(func.__name__,1000*(time.time()-t1))) ... return r ... return wrapper ... >>> @log ... def fast(x,y): ... return x+y ... >>> @log ... def slow(x,y,z): ... time.sleep(0.1234) ... return x*y*z ... >>> @log ... def fast(x,y): ... time.sleep(0.0012) ... return x+y ... >>> fast(3,5) fast excute in 2.0973682403564453 ms 8 >>> slow(4,5,6) slow excute in 124.2520809173584 ms 120