python函數編程-裝飾器decorator


函數是個對象,並且可以賦值給一個變量,通過變量也能調用該函數:

>>> 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM