python進(jìn)階:第八章(裝飾器使用技巧)

問(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市换团,隨后出現(xiàn)的幾起案子悉稠,更是在濱河造成了極大的恐慌,老刑警劉巖艘包,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件的猛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡想虎,警方通過(guò)查閱死者的電腦和手機(jī)卦尊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舌厨,“玉大人岂却,你說(shuō)我怎么就攤上這事∪雇郑” “怎么了躏哩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)揉燃。 經(jīng)常有香客問(wèn)我震庭,道長(zhǎng),這世上最難降的妖魔是什么你雌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任器联,我火速辦了婚禮,結(jié)果婚禮上婿崭,老公的妹妹穿的比我還像新娘拨拓。我一直安慰自己,他們只是感情好氓栈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布承匣。 她就那樣靜靜地躺著,像睡著了一般师抄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竟宋,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音形纺,去河邊找鬼丘侠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逐样,可吹牛的內(nèi)容都是我干的蜗字。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼脂新,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挪捕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起争便,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤级零,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后滞乙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體妄讯,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年酷宵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亥贸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浇垦,死狀恐怖炕置,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情男韧,我是刑警寧澤朴摊,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站此虑,受9級(jí)特大地震影響甚纲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朦前,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一介杆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧韭寸,春花似錦春哨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春凰荚,著一層夾襖步出監(jiān)牢的瞬間燃观,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工便瑟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缆毁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓胳徽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親爽彤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子养盗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)适篙,斷路器往核,智...
    卡卡羅2017閱讀 134,704評(píng)論 18 139
  • 7.1 創(chuàng)建裝飾器 與java中的裝飾器模式類似,其作用就是將一些多余的嚷节、能夠重復(fù)使用的代碼抽離出來(lái)聂儒,然后通過(guò)py...
    Lemon_Home閱讀 506評(píng)論 0 0
  • 前言 人生苦多,快來(lái) Kotlin 硫痰,快速學(xué)習(xí)Kotlin衩婚! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,218評(píng)論 9 118
  • 本文為《爬著學(xué)Python》系列第十篇文章效斑。 在實(shí)際操作中非春,可能函數(shù)是我們幾乎唯一的實(shí)現(xiàn)操作的方式,這是因?yàn)楹瘮?shù)能...
    SyPy閱讀 5,478評(píng)論 0 8
  • 畫(huà)牡丹前缓屠,擠出四種基礎(chǔ)顏料-藤黃奇昙、赭石、花青與 和曙紅敌完。 起筆水份飽滿些储耐,筆尖端部分調(diào)入曙紅,藏鋒畫(huà)起滨溉。 根據(jù)牡丹...
    鏡子靜閱讀 603評(píng)論 0 0