1Python進階強化訓練之裝飾器使用技巧進階
Python進階強化訓練之裝飾器使用技巧進階
如何使用函數裝飾器?
實際案例
某些時候我們想為多個函數,統一添加某種功能,比如記時統計、記錄日誌、緩存運算結果等等。
我們不想在每個函數內一一添加完全相同的代碼,有什麽好的解決方案呢?
解決方案
定義裝飾奇函數,用它來生成一個在原函數基礎添加了新功能的函數,替代原函數
如有如下兩道題:
題目一
斐波那契數列又稱黃金分割數列,指的是這樣一個數列:1,1,2,3,5,8,13,21,….,這個數列從第三項開始,每一項都等於前兩項之和,求數列第n項。
題目二
一個共有10個臺階的樓梯,從下面走到上面,一次只能邁1-3個臺階,並且不能後退,走完整個樓梯共有多少種方法?
腳本如下:
# 函數裝飾器 def memp(func): cache = {} def wrap(*args): if args not in cache: cache[args] = func(*args) return cache[args] return wrap # 第一題 @memp def fibonacci(n): if n <= 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2) print(fibonacci(50)) # 第二題 @memp def climb(n, steps): count = 0 if n == 0: count = 1 elif n > 0: for step in steps: count += climb(n - step, steps) return count print(climb(10, (1, 2, 3)))
輸出結果:
C:\Python\Python35\python.exe E:/python-intensive-training/s11.py 20365011074 274 Process finished with exit code 0
如何為被裝飾的函數保存元數據?
實際案例
在函數對象張保存著一些函數的元數據,例如:
方法 | 描述 |
---|---|
f.__name__ | 函數的名字 |
f.__doc__ | 函數文檔字符串 |
f.__module__ | 函數所屬模塊名 |
f.__dict__ | 屬性字典 |
f.__defaults__ | 默認參數元素 |
我們在使用裝飾器後,再使用上面的這些屬性訪問時,看到的是內部包裹函數的元數據,原來函數的元數據變丟失掉了,應該如何解決?
解決方案
使用標準庫functools
中的裝飾器wraps
裝飾內部包裹函數,可以指定將原來函數的某些屬性更新到包裹函數上面
from functools import wraps def mydecoratot(func): @wraps(func) def wrapper(*args, **kwargs): """wrapper function""" print("In wrapper") func(*args, **kwargs) return wrapper @mydecoratot def example(): """example function""" print(‘In example‘) print(example.__name__) print(example.__doc__)
輸出結果:
C:\Python\Python35\python.exe E:/python-intensive-training/s12.py example example function Process finished with exit code 0
如何定義帶參數的裝飾器?
實際案例
實現一個裝飾器,它用來檢查被裝飾函數的參數類型,裝飾器可以通過指定函數參數的類型,調用時如果檢測出類型不匹配則拋出異常,比如調用時可以寫成如下:
@typeassert(str, int, int) def f(a, b, c): ......
或者
@typeassert(y=list) def g(x, y): ......
解決方案
提取函數簽名:inspect.signature()
帶參數的裝飾器,也就是根據參數定制化一個裝飾器,可以看成生產裝飾器的工廠,美的調用typeassert
,返回一個特定的裝飾器,然後用他去裝飾其他函數。
from inspect import signature def typeassery(*ty_args, **ty_kwargs): def decorator(func): # 獲取到函數參數和類型之前的關系 sig = signature(func) btypes = sig.bind_partial(*ty_args, **ty_kwargs).arguments def wrapper(*args, **kwargs): for name, obj in sig.bind(*args, **kwargs).arguments.items(): if name in btypes: if not isinstance(obj, btypes[name]): raise TypeError(‘"%s" must be "%s" ‘ % (name, btypes[name])) return func(*args, **kwargs) return wrapper return decorator @typeassery(int, str, list) def f(a, b, c): print(a, b, c) # 正確的 f(1, ‘abc‘, [1, 2, 3]) # 錯誤的 f(1, 2, [1, 2, 3])
執行結果
C:\Python\Python35\python.exe E:/python-intensive-training/s13.py 1 abc [1, 2, 3] Traceback (most recent call last): File "E:/python-intensive-training/s13.py", line 28, in <module> f(1, 2, [1, 2, 3]) File "E:/python-intensive-training/s13.py", line 14, in wrapper raise TypeError(‘"%s" must be "%s" ‘ % (name, btypes[name])) TypeError: "b" must be "<class ‘str‘>" Process finished with exit code 1
如何實現屬性可修改的函數裝飾器?
實際案例
為分析程序內那些函數執行時間開銷較大,我們定義一個帶timeout參數的函數裝飾器,裝飾功能如下:
統計被裝飾函數單詞調用運行時間
時間大於參數timeout的,將此次函數調用記錄到log日誌中
運行時可修改timeout的值
解決方案
為包裹函數增加一個函數,用來修改閉包中使用的自由變量
在python3中使用nonlocal訪問嵌套作用於中的變量引用
代碼如下:
from functools import wraps import time import logging from random import randint def warn(timeout): # timeout = [timeout] # py2 def decorator(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) used = time.time() - start if used > timeout: # if used > timeout: # py2 msg = ‘"%s": "%s" > "%s"‘ % (func.__name__, used, timeout) # msg = ‘"%s": "%s" > "%s"‘ % (func.__name__, used, timeout[0]) # py2 logging.warn(msg) return res def setTimeout(k): nonlocal timeout timeout = k # timeout[0] = k # py2 wrapper.setTimeout = setTimeout return wrapper return decorator @warn(1.5) def test(): print(‘In Tst‘) while randint(0, 1): time.sleep(0.5) for _ in range(10): test() test.setTimeout(1) for _ in range(10): test()
輸出結果:
C:\Python\Python35\python.exe E:/python-intensive-training/s14.py In Tst In Tst WARNING:root:"test": "2.503000259399414" > "1.5" In Tst In Tst In Tst In Tst In Tst In Tst In Tst In Tst In Tst WARNING:root:"test": "1.0008063316345215" > "1" In Tst In Tst In Tst WARNING:root:"test": "1.0009682178497314" > "1" In Tst In Tst WARNING:root:"test": "1.5025172233581543" > "1" In Tst In Tst In Tst In Tst Process finished with exit code 0
如何在類中定義裝飾器?
實際案例
實現一個能將函數調用信息記錄到日誌的裝飾器:
把每次函數的調用時間,執行時間,調用次數寫入日誌
可以對被裝飾函數分組,調用信息記錄到不同日誌
動態修改參數,比如日誌格式
動態打開關閉日誌輸出功能
解決方案
為了讓裝飾器在使用上更加靈活,可以把類的實例方法作為裝飾器,此時包裹函數中就可以持有實例對象,便於修改屬性和擴展功能
代碼如下:
import logging from time import localtime, time, strftime, sleep from random import choice class CallingInfo: def __init__(self, name): log = logging.getLogger(name) log.setLevel(logging.INFO) fh = logging.FileHandler(name + ‘.log‘) # 日誌保存的文件 log.addHandler(fh) log.info(‘Start‘.center(50, ‘-‘)) self.log = log self.formattter = ‘%(func)s -> [%(time)s - %(used)s - %(ncalls)s]‘ def info(self, func): def wrapper(*args, **kwargs): wrapper.ncalls += 1 lt = localtime() start = time() res = func(*args, **kwargs) used = time() - start info = {} info[‘func‘] = func.__name__ info[‘time‘] = strftime(‘%x %x‘, lt) info[‘used‘] = used info[‘ncalls‘] = wrapper.ncalls msg = self.formattter % info self.log.info(msg) return res wrapper.ncalls = 0 return wrapper def SetFormatter(self, formatter): self.formattter = formatter def turnOm(self): self.log.setLevel(logging.INFO) def turnOff(self): self.log.setLevel(logging.WARN) cinfo1 = CallingInfo(‘mylog1‘) cinfo2 = CallingInfo(‘mylog2‘) # 設置日誌指定格式 # cinfo1.SetFormatter(‘%(func)s -> [%(time)s - %(ncalls)s]‘) # 關閉日誌 # cinfo2.turnOff() @cinfo1.info def f(): print(‘in F‘) @cinfo1.info def g(): print(‘in G‘) @cinfo2.info def h(): print(‘in H‘) for _ in range(50): choice([f, g, h])() sleep(choice([0.5, 1, 1.5]))
#裝飾器
本文出自 “Eden” 博客,請務必保留此出處http://edeny.blog.51cto.com/10733491/1924942
1Python進階強化訓練之裝飾器使用技巧進階