問(wèn)題一:如何使用函數(shù)裝飾器?
問(wèn)題內(nèi)容:
某些時(shí)候我們想為多個(gè)函數(shù),統(tǒng)一添加某種功能笼裳,比如計(jì)時(shí)統(tǒng)計(jì),記錄日志粱玲,緩存運(yùn)算結(jié)果等等躬柬。
題目一:
裴波那契數(shù)列(Fibonacci Sequence),又稱黃金分割數(shù)列密幔,指的是這樣一個(gè)數(shù)列:1楔脯,1,2胯甩,3昧廷,5,8偎箫,13木柬,21,.......淹办,這個(gè)數(shù)列從第三項(xiàng)開(kāi)始眉枕,每一項(xiàng)都等于前兩項(xiàng)之和,求數(shù)列第n項(xiàng)怜森。
In [1]: def fibonacci(n):
...: if n<= 1:
...: return 1
...: return fibonacci(n - 1) + fibonacci(n - 2)
...:
上面的方法沒(méi)有使用緩存速挑,每次計(jì)算都要重復(fù)計(jì)算前面的數(shù)值。
In [2]: def fibonacci(n,cache):
...: if cache is None:
...: cache = {}
...:
...: if n in cache:
...: return cache[n]
...:
...: if n<= 1:
...: return 1
...: cache[n] = fibonacci(n - 1,cache) + fibonacci(n - 2,cache)
...: return cache[n]
...:
...:
我們引入緩存副硅,這樣計(jì)算的速度將會(huì)加快很多姥宝。
In [8]: print(fibonacci(20,{}))
10946
題目2:一個(gè)共有10個(gè)臺(tái)階的樓梯,從下面走到上面恐疲,一次只能邁1-3個(gè)臺(tái)階腊满,并且不能后退套么,走完這個(gè)樓梯共有多少種方法。
In [9]: 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
...:
使用裝飾器:
In [16]: def memo(func):
...: cache = {}
...: def warp(*args):
...: if args not in cache:
...: cache[args] = func(*args)
...: return cache[args]
...: return warp
...:
In [17]: @memo
...: 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
...:
In [18]: climb(10,(1,2,3))
Out[18]: 274
如何為被裝飾的函數(shù)保存元數(shù)據(jù)碳蛋?
問(wèn)題內(nèi)容:
在函數(shù)對(duì)象中保存著一些函數(shù)的元數(shù)據(jù)胚泌,例如:
f.__name__ : 函數(shù)的名字
f.__doc__ : 函數(shù)文檔字符串
f.__moudle__ : 函數(shù)所屬模塊名
f.__dict__ : 屬性字典
f.__defaults__ : 默認(rèn)參數(shù)元組
......
我們?cè)谑褂醚b飾器后,再使用上面這些屬性訪問(wèn)時(shí)肃弟,看到的是內(nèi)部包裹函數(shù)的元數(shù)據(jù)玷室,原來(lái)函數(shù)的元數(shù)據(jù)丟失掉了,應(yīng)該如何解決笤受?
解決方案:
使用標(biāo)準(zhǔn)庫(kù)functools中的裝飾器wraps裝飾內(nèi)部包裹函數(shù)阵苇,可以指定將原函數(shù)的某些屬性,更新到包裹函數(shù)上面感论。
我們新建一個(gè)函數(shù)绅项,看看有哪些元數(shù)據(jù)
In [1]: def f(a):
...: '''f function'''
...: return a*2
...:
In [2]: f.__
f.__annotations__ f.__delattr__ f.__ge__ f.__init__ f.__ne__ f.__setattr__
f.__call__ f.__dict__ f.__get__ f.__kwdefaults__ f.__new__ f.__sizeof__
f.__class__ f.__dir__ f.__getattribute__ f.__le__ f.__qualname__ f.__str__
f.__closure__ f.__doc__ f.__globals__ f.__lt__ f.__reduce__ f.__subclasshook__
f.__code__ f.__eq__ f.__gt__ f.__module__ f.__reduce_ex__
f.__defaults__ f.__format__ f.__hash__ f.__name__ f.__repr__
In [2]: f.__name__
Out[2]: 'f'
In [3]: g = f
In [4]: g.__name__
Out[4]: 'f'
我們發(fā)現(xiàn),結(jié)果和引用函數(shù)的變量名無(wú)關(guān)比肄,只要是同一個(gè)對(duì)象
In [5]: f.__doc__
Out[5]: 'f function'
函數(shù)所屬哪個(gè)模塊
In [6]: f.__module__
Out[6]: '__main__'
再看下保存默認(rèn)參數(shù)的defaults快耿,在python中默認(rèn)參數(shù)不是每次調(diào)用的時(shí)創(chuàng)建的,而是在定義函數(shù)的時(shí)候芳绩,直接創(chuàng)建好了掀亥。
In [8]: def f(a,b=1,c=[]):
...: print(a,b,c)
...:
In [9]: f.__defaults__
Out[9]: (1, [])
我們?cè)谡{(diào)用之前實(shí)現(xiàn)賦值給默認(rèn)的list
In [10]: f.__defaults__[1].append('abc')
在調(diào)用之前,已經(jīng)創(chuàng)建
In [12]: f.__defaults__
Out[12]: (1, ['abc'])
In [11]: f(100)
100 1 ['abc']
在默認(rèn)參數(shù)中妥色,盡量不要使用可變變量搪花。
查看函數(shù)的包裹函數(shù)
In [14]: def f():
...: a = 2
...: return lambda k : a ** k
...:
In [15]: g = f()
In [16]: g.__closure__
Out[16]: (<cell at 0x000001C8209745E8: int object at 0x00000000771A01F0>,)
In [17]: c = g.__closure__[0]
In [18]: c.cell_contents
Out[18]: 2
當(dāng)裝飾器存在的時(shí)候,元數(shù)據(jù)變成了包裹函數(shù)的
In [21]: def mydecorator(func):
...: def wrapper(*args,**kargs):
...: '''wrapper function'''
...: print("In wrapper")
...: func(*args,**kargs)
...: return wrapper
...:
In [22]: @mydecorator
...: def example():
...: """example function"""
...: print("In example")
...:
In [23]: example.__name__
Out[23]: 'wrapper'
In [24]: example.__doc__
Out[24]: 'wrapper function'
我們使用wrapper模塊
In [25]: from functools import update_wrapper,wraps
In [26]: def mydecorator(func):
...: def wrapper(*args,**kargs):
...: '''wrapper function'''
...: print("In wrapper")
...: func(*args,**kargs)
...: update_wrapper(wrapper,func,("__name__","__doc__"),("__dict__",))
...: return wrapper
...:
...:
In [27]: @mydecorator
...: def example():
...: """example function"""
...: print("In example")
...:
In [28]: example.__name__
Out[28]: 'example'
In [29]: example.__doc__
Out[29]: 'example function'
通過(guò)后面?zhèn)€兩個(gè)元組嘹害,我們指定不能被包裹函數(shù)覆蓋的元數(shù)據(jù)撮竿。
我們也可以使用默認(rèn)參數(shù)。
In [30]: from functools import update_wrapper,wraps,WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES
In [31]: def mydecorator(func):
...: def wrapper(*args,**kargs):
...: '''wrapper function'''
...: print("In wrapper")
...: func(*args,**kargs)
...: update_wrapper(wrapper,func)
...: return wrapper
...:
...:
In [32]: print(WRAPPER_ASSIGNMENTS)
('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
In [33]: print(WRAPPER_UPDATES)
('__dict__',)
更簡(jiǎn)便的方式是直接使用wraps函數(shù)裝飾內(nèi)部包裹函數(shù)
In [34]: def mydecorator(func):
...: @wraps(func)
...: def wrapper(*args,**kargs):
...: '''wrapper function'''
...: print("In wrapper")
...: func(*args,**kargs)
...: #update_wrapper(wrapper,func)
...: return wrapper
...:
...:
In [35]: @mydecorator
...: def example():
...: """example function"""
...: print("In example")
...:
In [36]: example.__name__
Out[36]: 'example'
In [37]: example.__doc__
Out[37]: 'example function'
問(wèn)題三:如何定義帶參數(shù)的裝飾器笔呀?
實(shí)現(xiàn)一個(gè)裝飾器幢踏,它用來(lái)檢查被裝飾函數(shù)的參數(shù)類型。裝飾器可以通過(guò)參數(shù)指明函數(shù)參數(shù)的類型许师,調(diào)用時(shí)如果檢測(cè)出類型不匹配則拋出異常房蝉。
@typeassert(str,int,int)
def f(a,b,c):
......
@typeassert(y=list)
def g(x,y):
......
解決方案:
提取函數(shù)簽名:inspect.signature()
帶參數(shù)的裝飾器,也就是根據(jù)參數(shù)定制話一個(gè)裝飾器微渠,可以看成生產(chǎn)裝飾器的工廠搭幻。每次調(diào)用typeassert,返回一個(gè)特定的裝飾器逞盆,然后用它去裝飾其它函數(shù)檀蹋。
我們使用signature獲得參數(shù)
In [1]: from inspect import signature
In [2]: def f(a,b,c=1):pass
獲取簽名
In [3]: sig = signature(f)
獲取參數(shù)字典
In [4]: sig.parameters
Out[4]:
mappingproxy({'a': <Parameter "a">,
'b': <Parameter "b">,
'c': <Parameter "c=1">})
In [6]: a = sig.parameters['a']
In [7]: a.name
Out[7]: 'a'
In [8]: a.default
Out[8]: inspect._empty
In [9]: c = sig.parameters['c']
In [10]: c.default
Out[10]: 1
我們實(shí)現(xiàn)參數(shù)類型和參數(shù)的綁定
In [14]: sig.bind(str,int,int)
Out[14]: <BoundArguments (a=<class 'str'>, b=<class 'int'>, c=<class 'int'>)>
In [15]: bargs = sig.bind(str,int,int)
生成對(duì)應(yīng)字典
In [16]: bargs.arguments
Out[16]: OrderedDict([('a', str), ('b', int), ('c', int)])
In [17]: bargs.arguments['a']
Out[17]: str
注意一點(diǎn)是,bind()函數(shù)要求全部參數(shù)復(fù)制纳击,當(dāng)缺少的時(shí)候续扔,則會(huì)報(bào)錯(cuò)
我們可以使用bind_partial()
In [18]: sig.bind_partial(str)
Out[18]: <BoundArguments (a=<class 'str'>)>
會(huì)按順序綁定
下面我們寫(xiě)帶參數(shù)的裝飾器:
In [13]: def typeassert(*ty_args,**ty_kargs):
...: def decorator(func):
...: # 這里獲得函數(shù)參數(shù)和類型的關(guān)系 func -> a,b (獲得函數(shù)簽名)
...: # d = {'a':int,'b':str} 通過(guò)參數(shù)獲得關(guān)系映射的字典
...: def wrapper(*args,**kargs):
...: # 迭代參數(shù),判斷是否在d字典中 arg in d,instance(arg,d[arg])
...: return func(*args,**kargs)
...: return wrapper
...: return decorator
...:
現(xiàn)在我們將上面的框架補(bǔ)全:
In [19]: def typeassert(*ty_args,**ty_kargs):
...: def decorator(func):
...: sig = signature(func)
...: btypes = sig.bind_partial(*ty_args,**ty_kargs).arguments
...: def wrapper(*args,**kargs):
...: for name,obj in sig.bind(*args,**kargs).arguments.items():
...: if name in btypes:
...: if not isinstance(obj,btypes[name]):
...: raise TypeError('"%s" must be "%s"' % (name,btypes[name]))
...: return func(*args,**kargs)
...: return wrapper
...: return decorator
...:
In [20]: @typeassert(int,str,list)
...: def f(a,b,c):
...: print(a,b,c)
...:
In [21]: f(1,'abc',[1,2,3])
1 abc [1, 2, 3]
In [22]: f(1,2,[1,2,3])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-22-1c6f13da3d30> in <module>()
----> 1 f(1,2,[1,2,3])
<ipython-input-19-299c61c8c1c1> in wrapper(*args, **kargs)
7 if name in btypes:
8 if not isinstance(obj,btypes[name]):
----> 9 raise TypeError('"%s" must be "%s"' % (name,btypes[name]))
10 return func(*args,**kargs)
11 return wrapper
TypeError: "b" must be "<class 'str'>"
這里有一點(diǎn)不明白 if name in btypes焕数。
如何實(shí)現(xiàn)屬性可修改的函數(shù)裝飾器纱昧?
問(wèn)題內(nèi)容:
為分析程序內(nèi)有哪些函數(shù)執(zhí)行時(shí)間開(kāi)銷較大,我們定義一個(gè)帶timeout參數(shù)的函數(shù)裝飾器堡赔。裝飾器功能如下:
1识脆,統(tǒng)計(jì)被裝飾函數(shù)單次調(diào)用運(yùn)行時(shí)間
2,時(shí)間大于參數(shù)timeout的善已,將此次函數(shù)調(diào)用記錄到log日志中
3灼捂,運(yùn)行時(shí)可修改timeoout的值
In [1]: from functools import wraps
In [2]: import time
In [3]: import logging
In [4]: def warn(timeout):
...: def decorator(func):
...: def wrapper(*args,**kargs):
...: start = time.time()
...: res = func(*args,**kargs)
...: used = time.time() - start
...: if used > timeout:
...: msg = '"%s": %s > %s' % (func.__name__,used,timeout)
...: logging.warn(msg)
...: return res
...: return wrapper
...: return decorator
...:
In [5]: from random import randint
In [6]: @warn(1.5)
...: def test():
...: print("In test")
...: while randint(0,1):
...: time.sleep(0.5)
...:
In [7]: for _ in range(30):
...: test()
...:
In test
In test
In test
C:\Users\wex\AppData\Local\Programs\Python\Python35\Scripts\ipython:9: DeprecationWarning: The 'warn' function is deprecated, use 'warning' instead
WARNING:root:"test": 1.5016095638275146 > 1.5
In test
In test
In test
In test
In test
In test
In test
In test
WARNING:root:"test": 1.5012586116790771 > 1.5
In test
In test
WARNING:root:"test": 3.0051050186157227 > 1.5
In test
WARNING:root:"test": 2.0030357837677 > 1.5
In test
In test
In test
In test
In test
In test
In test
WARNING:root:"test": 1.5070884227752686 > 1.5
In test
WARNING:root:"test": 2.003775119781494 > 1.5
In test
In test
In test
In test
In test
In test
In test
In test