函數裝飾器用於在源碼中「標記」函數, 以某種方式加強函數的行爲,這是一個強大的功能。python
函數裝飾器是一個可調用對象,其參數是另一個函數,即被裝飾函數。裝飾器可能處理被裝飾函數,而後將其返回,或者將其替換成另外一個函數或可調用對象。數組
函數裝飾器的一個重要特性就是,它們在被裝飾的函數定義以後當即運行。緩存
registry = [] def register(func): print('running register(%s)' % func) registry.append(func) return func @register def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): print('running f3()') def main(): print('running main()') print('registry ->', registry) f1() f2() f3() if __name__ == '__main__': main()
運行結果:閉包
running register(<function f1 at 0x0000022DC2D23620>) running register(<function f2 at 0x0000022DC2D236A8>) running main() registry -> [<function f1 at 0x0000022DC2D23620>, <function f2 at 0x0000022DC2D236A8>] running f1() running f2() running f3()
能夠看到,被裝飾的 f1() 和 f2() 首先被運行,隨後才運行main()中的語句。app
被裝飾函數運行時,其自己的內容(示例中print語句)並無被執行,而是運行了裝飾器函數中的print語句;這就是裝飾器的做用,替代被裝飾函數,同時裝飾器也能夠調用外界自由變量(registry),從而引出一個重要概念:函數
實例中registry變量和register函數組合的共同體,被成稱爲閉包。code
該例中有兩個不太尋常的地方:orm
# clockdeco.py 輸出被裝飾函數的運行時間 import time def clock(func): def clocked(*args): t0 = time.perf_counter() result = func(*args) elapsed = time.perf_counter() - t0 name = func.__name__ arg_str = ','.join(repr(arg) for arg in args) print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) return result return clocked
簡單運用:對象
# clockdeco_demo.py import time from clockdeco import clock @clock def snooze(seconds): time.sleep(seconds) @clock def factorial(n): return 1 if n < 2 else n*factorial(n-1) def main(): print('*' * 40, 'Calling snooze(.123)') snooze(.123) print('*' * 40, 'Calling factorial(6)') print('6! =', factorial(6)) if __name__ == '__main__': main()
運行結果:ci
**************************************** Calling snooze(.123) [0.12240868s] snooze(0.123) -> None **************************************** Calling factorial(6) [0.00000068s] factorial(1) -> 1 [0.00020317s] factorial(2) -> 2 [0.00039755s] factorial(3) -> 6 [0.00053638s] factorial(4) -> 24 [0.00062375s] factorial(5) -> 120 [0.00067319s] factorial(6) -> 720 6! = 720
運行過程當中,首先輸出裝飾器函數中的內容:
而後獲得最終的計算結果。可見,裝飾器函數的優先級較高
固然,該實例中的裝飾器具備幾個缺點:
下面的例子對其作出改進:
# clockdeco2.py import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(','.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(', '.join(pairs)) arg_str = ', '.join(arg_lst) print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result)) return result return clocked
運用:
# clockdeco_demo.py import time from clockdeco2 import clock @clock def snooze(seconds): time.sleep(seconds) @clock def factorial(n): return 1 if n < 2 else n*factorial(n-1) def main(): print('*' * 40, 'Calling snooze(.123)') snooze(.123) print('*' * 40, 'Calling factorial(6)') print('6! =', factorial(6)) if __name__ == '__main__': main()
運行結果:
**************************************** Calling snooze(.123) [0.12328553s] snooze(0.123) -> None **************************************** Calling factorial(6) [0.00000000s] factorial(1) -> 1 [0.00000000s] factorial(2) -> 2 [0.00000000s] factorial(3) -> 6 [0.00099683s] factorial(4) -> 24 [0.00099683s] factorial(5) -> 120 [0.00099683s] factorial(6) -> 720 6! = 720
改進後的clockdeco2.py中,使用functools.wraps裝飾器把相關屬性從func複製到clocked中,此外,這個新版本還能正確處理關鍵字參數。functools.wraps是標準庫中的裝飾器,它能夠用於裝飾一個函數,可是對於被裝飾函數自己的功能沒有任何影響,它的功能只是傳遞函數內置參數。
# clockdeco_param.py import time DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' def clock(fmt=DEFAULT_FMT): def decorate(func): def clocked(*_args): t0 = time.time() _result = func(*_args) elapsed = time.time() - t0 name = func.__name__ args = ','.join(repr(arg) for arg in _args) result = repr(_result) print(fmt.format(**locals())) return _result return clocked return decorate
示例1:
# clockdeco_param_demo.py import time from clockdeco_param import clock @clock() def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
運行結果:
[0.12367034s] snooze(0.123) -> None [0.12367010s] snooze(0.123) -> None [0.12366986s] snooze(0.123) -> None
示例2:
# clockdeco_param_demo.py import time from clockdeco_param import clock @clock('{name}:{elapsed}s') def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
運行結果:
snooze:0.12366843223571777s snooze:0.12369871139526367s snooze:0.12366509437561035s
示例3:
# clockdeco_param_demo.py import time from clockdeco_param import clock @clock('{name}({args}) dt={elapsed:0.3f}s') def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
運行結果:
snooze(0.123) dt=0.124s snooze(0.123) dt=0.124s snooze(0.123) dt=0.124s
分析三個示例能夠看出,當裝飾器clock的參數不一樣時,被裝飾函數運行所得結果也會不一樣。
python中參數化裝飾器的用意在於將更多的參數傳送給裝飾器,由於裝飾器的第一個參數必定是被裝飾函數。
functools.lru_cache和functools.wraps同樣,也是一個python內置裝飾器,它的功能是將耗時的函數結果保存起來,避免傳圖相同的參數形成重複計算,從而節省代碼運行時間。
下面以斐波那契數列寫一個案例:
使用functools.lru_cache
import functools
from clockdeco import clock
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2: return n return fibonacci(n-2) + fibonacci(n-1)
if __name__=='__main__':
print(fibonacci(30))
運行結果:
[0.00000000s] fibonacci(0) -> 0 [0.00000068s] fibonacci(1) -> 1 ...... [0.00000271s] fibonacci(29) -> 514229 [0.00542815s] fibonacci(30) -> 832040 832040
屢次運行,計算fibonacci(30)大概耗時0.005秒左右
做爲對比:
import functools from clockdeco import clock @clock def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__=='__main__': print(fibonacci(30))
運行結果:
....... [156.42139917s] fibonacci(28) -> 317811 [230.80184171s] fibonacci(29) -> 514229 [368.52227404s] fibonacci(30) -> 832040 832040
嗯……陷入沉思,雖然筆記本渣渣配置,可是運行了6分鐘,差距太大