python裝飾器學(xué)習(xí)詳解-函數(shù)部分

本文的文字及圖片來源于網(wǎng)絡(luò),僅供學(xué)習(xí)郁轻、交流使用,不具有任何商業(yè)用途,如有問題請(qǐng)及時(shí)聯(lián)系我們以作處理

最近閱讀《流暢的python》看見其用函數(shù)寫裝飾器部分寫的很好,想寫一些自己的讀書筆記躏仇。眾所周知昙楚,裝飾器是python學(xué)習(xí)過程中的一道門檻,初學(xué)者學(xué)習(xí)時(shí)往往是知其然溃肪,不知其所以然争涌,這樣的結(jié)果是導(dǎo)致一段時(shí)間后會(huì)遺忘掉該部分內(nèi)容粉楚,只好再次去學(xué)習(xí),拉高了學(xué)習(xí)成本。

想學(xué)好python的裝飾器模软,需要明白一下幾點(diǎn)伟骨;

1:閉包

1)函數(shù)嵌套

2)內(nèi)部函數(shù)使用外部函數(shù)的變量

3)外部函數(shù)的返回值為內(nèi)部函數(shù)

? 接下來看看《流暢的python》中的例子,我稍微修改了一下:

>>> def make_averager(series=[]):

...? ? def averager(new_value):

...? ? ? ? ? ? series.append(new_value)

...? ? ? ? ? ? total = sum(series)

...? ? ? ? ? ? return total/len(series)

...? ? return averager

...

>>> avg = make_averager()

>>> avg

<function make_averager.<locals>.averager at 0x10b82cb00>

>>> avg(10)

10.0

>>> avg(11)

10.5

>>> avg(12)

11.0

? 函數(shù) make_averager 實(shí)現(xiàn)了一個(gè) 計(jì)算當(dāng)前所有數(shù)字的平均值的功能燃异,不斷的添加一個(gè)值携狭,然后計(jì)算當(dāng)前的平均值。

? avg這個(gè)對(duì)象內(nèi)存地址指向了make_averager這個(gè)函數(shù)的內(nèi)部函數(shù)中回俐,而且avg通過不斷的添加值進(jìn)行平均值計(jì)算逛腿,按理說在這個(gè)內(nèi)部函數(shù)沒有存儲(chǔ)new_value的空間,而且在make_averager對(duì)avg賦值后仅颇,函數(shù)返回后series這個(gè)變量也應(yīng)該消失了单默,但是avg卻依然可以進(jìn)行計(jì)算。

? 這就是閉包忘瓦,內(nèi)部函數(shù)averager使用外面的自由變量搁廓,也就是屬于make_averager的局部變量series

>>> avg.__code__.co_varnames

('new_value', 'total')

>>> avg.__code__.co_freevars

('series',)

? 可以發(fā)現(xiàn)avg的自由變量是make_averager的局部變量,就是說閉包里的內(nèi)部函數(shù)可以使用外部函數(shù)的變量耕皮,即我們上面提到的第二點(diǎn):“內(nèi)部函數(shù)使用外部函數(shù)的變量”境蜕, 注:自由變量只能read,并不能write明场,不然會(huì)提示本地變量并沒有賦值的錯(cuò)誤,我們舉的例子沒遇到這個(gè)問題李丰,因?yàn)槲覀儧]有給 series 賦值苦锨,我們只是調(diào) 用 series.append,并把它傳給 sum 和 len趴泌。也就是說舟舒,我們利用了 列表是可變的對(duì)象這一事實(shí) 。下圖是書中提供的閉包范圍圖:

?

2:裝飾器的實(shí)現(xiàn)

所謂裝飾器嗜憔,就是在不改變基礎(chǔ)函數(shù)的功能上再次給它封裝一層秃励,達(dá)到我們想要的目的,接下來我舉個(gè)簡單的例子:

? deco_demo.py

1 def col(func):

? 2? ? def inner(*args, **kwargs):

? 3? ? ? ? print(func.__name__)

? 4? ? ? ? print(locals())

? 5? ? ? ? print(inner.__code__.co_varnames)

? 6? ? ? ? print(inner.__code__.co_freevars)

? 7? ? ? ? return func(*args, **kwargs)

? 8? ? return inner

? 9

10

11 @col

12 def new_add(x):

13? ? return x+2

14

15

16 def new_add_1(x):

17? ? return x+3

18

19

20 print(new_add(3))

21

22 new_add_1 = col(new_add_1)

23 print(new_add_1(3))

下方是它的返回結(jié)果:

new_add

{'args': (3,), 'kwargs': {}, 'func': <function new_add at 0x10d32aa70>, 'inner': <function col.<locals>.inner at 0x10d32acb0>}

('args', 'kwargs')

('func', 'inner')

5

new_add_1

{'args': (3,), 'kwargs': {}, 'func': <function new_add_1 at 0x10d32add0>, 'inner': <function col.<locals>.inner at 0x10d32a8c0>}

('args', 'kwargs')

('func', 'inner')

6

1-8:是定義的一個(gè)簡單裝飾器吉捶,

3:打印當(dāng)被裝飾函數(shù)的名字

4:打印inner這個(gè)內(nèi)部函數(shù)中的所有變量

5:打印當(dāng)前inner的局部變量夺鲜;

6:則打印自由變量;

11-13:修飾了一個(gè)簡單函數(shù)

16呐舔,22币励,23:@這個(gè)語法糖,背后實(shí)現(xiàn)的過程珊拼;

? 也就是說col(new_add)返回的是當(dāng)前的內(nèi)部函數(shù)的內(nèi)存地址食呻,而這個(gè)調(diào)用這個(gè)內(nèi)部函數(shù)時(shí)會(huì)使用自由變量func即col的局部變量,進(jìn)而達(dá)到裝飾器的目的;

有參數(shù)的裝飾器實(shí)現(xiàn)

? 既然無參數(shù)的裝飾器即@col 仅胞,通過內(nèi)部函數(shù)的方式裝飾基礎(chǔ)函數(shù)每辟,那么我們調(diào)用有參數(shù)的裝飾器 則可以再原本的基礎(chǔ)即函數(shù)col再封裝一層函數(shù),使其達(dá)到可以通過裝飾器傳參數(shù)的目的

1 from functools import wraps

? 2

? 3

? 4 def col(string="hello world"):

? 5? ? def decorate(func):

? 6? ? ? ? @wraps(func)

? 7? ? ? ? def inner(*args, **kwargs):

? 8? ? ? ? ? ? print(string)

? 9? ? ? ? ? ? return func(*args, **kwargs)

10? ? ? ? return inner

11? ? return decorate

12

13

14 @col()

15 def new_add(x):

16? ? return x+2

17

18

19 @col("hello python")

20 def new_add_1(x):

21? ? return x+3

22

23

24 def new_add_2(x):

25? ? return x+4

26

27

28 print(new_add(1))

29 print(new_add_1(1))

30

31

32 new_add_2 = col("hello china")(new_add_2)

33 print(new_add_2(1))

導(dǎo)入wrap是為了修復(fù)這個(gè)裝飾器的名稱干旧,new_add.__name__調(diào)用時(shí)指向被裝飾的函數(shù)渠欺,而不是內(nèi)部函數(shù),有興趣的小伙伴可以去了解一下莱革;

4-11:實(shí)現(xiàn)了一個(gè)帶參數(shù)的裝飾器峻堰,最外層返回的是我們真正的裝飾器;

32-33:則是@這個(gè)裝飾器語法糖背后的實(shí)現(xiàn)過程

可以發(fā)現(xiàn)new_add與new_add_1這兩個(gè)函數(shù)的裝飾器是兩個(gè)不同值盅视,而我們的裝飾器也返回了不同的對(duì)應(yīng)情況

hello world

3

hello python

4

hello china

5

間而言之:裝飾器就是在我們需要添加功能的函數(shù)上進(jìn)而封裝一層捐名,而python的語法糖@背后,幫助我們省略掉了這些賦值的過程闹击;

3:裝飾器何時(shí)調(diào)用

關(guān)于裝飾器何時(shí)運(yùn)行镶蹋,我們分兩種情況討論,一種是當(dāng)作腳本運(yùn)行時(shí)赏半,另一種是當(dāng)作模塊被導(dǎo)入時(shí)贺归;

1 registry = []

? 2

? 3

? 4 def register(func):

? 5? ? print(f"running register {func}")

? 6? ? registry.append(func)

? 7? ? return func

? 8

? 9

10 @register

11 def f1():

12? ? print('running f1()')

13

14

15 @register

16 def f2():

17? ? print('running f2()')

18

19

20 def f3():

21? ? print('running f3()')

22

23

24 def main():

25? ? print('running main()')

26? ? print('regisry ->', registry)

27? ? f1()

28? ? f2()

29? ? f3()

30

31

32 if __name__ == '__main__':

33? ? main()

當(dāng)作獨(dú)立腳本運(yùn)行時(shí):

running register <function f1 at 0x103f9dcb0>

running register <function f2 at 0x103f9ddd0>

running main()

regisry -> [<function f1 at 0x103f9dcb0>, <function f2 at 0x103f9ddd0>]

running f1()

running f2()

running f3()

被當(dāng)作模塊導(dǎo)入時(shí):

>>> import registration

running register <function f1 at 0x1005a2710>

running register <function f2 at 0x1005a2b90>

>>> registration.registry

[<function f1 at 0x1005a2710>, <function f2 at 0x1005a2b90>]

該段代碼的裝飾器主要功能是:記錄了被裝飾函數(shù)的個(gè)數(shù),通常是web框架以這種方式把函數(shù)注冊(cè)到中央注冊(cè)器的某處断箫。

總結(jié):可以發(fā)現(xiàn)裝飾器無論是作為模塊被導(dǎo)入拂酣,還是單獨(dú)的腳本運(yùn)行,它都是優(yōu)先執(zhí)行的仲义;

4:裝飾器的常用模塊

之前介紹的function.wraps不用說了婶熬,接下來介紹兩種神奇的裝飾器;

1:singledispatch

何為singledispatch ?

就是在不改變函數(shù)本身的功能上復(fù)用該函數(shù)埃撵,達(dá)到重復(fù)使用函數(shù)名的目的赵颅,有點(diǎn)類似多態(tài)的感覺;可以把整體方案拆分成多個(gè)模塊暂刘,甚至可以為你無法修改的類提供專門的函數(shù)饺谬。使用@singledispatch 裝飾的普通函數(shù)會(huì)變成泛函數(shù)(generic function);根據(jù)第一個(gè)參數(shù)的類型谣拣,以不同方式執(zhí)行相同操作的一組函數(shù)

1 from functools import singledispatch

? 2

? 3

? 4 @singledispatch

? 5 def hello(obj):

? 6? ? print(obj)

? 7

? 8

? 9 @hello.register(str)

10 def _(text):

11? ? print("hello world "+text)

12

13

14 @hello.register(int)

15 def _(n):

16? ? print(n)

17

18

19 hello({"what": "say"})

20 print('*'*30)

21 hello('dengxuan')

22 print('*'*30)

23 hello(123)

{'what': 'say'}

******************************

hello world dengxuan

******************************

123

從該段代碼中我們可以發(fā)現(xiàn)募寨,當(dāng)使用singledispatch這個(gè)裝飾器時(shí),函數(shù)hello可以根據(jù)不同的參數(shù)返回不同的結(jié)果森缠。這樣的好處就是極大的減少代碼中的if/elif/else绪商,并且可以復(fù)用函數(shù)名稱 _ (下橫線代表沒用),降低了代碼的耦合度辅鲸,達(dá)到了多態(tài)的效果格郁。

2:lru_cache

根據(jù)書上原話:

functools.lru_cache 是非常實(shí)用的裝飾器,它實(shí)現(xiàn)了備忘 (memoization)功能。這是一項(xiàng)優(yōu)化技術(shù)例书,它把耗時(shí)的函數(shù)的結(jié)果保存 起來锣尉,避免傳入相同的參數(shù)時(shí)重復(fù)計(jì)算。LRU 三個(gè)字母是“Least Recently Used”的縮寫决采,表明緩存不會(huì)無限制增長自沧,一段時(shí)間不用的緩存 條目會(huì)被扔掉。

1 from my_tools.runtime import clock

? 2 import functools

? 3

? 4

? 5 @functools.lru_cache()

? 6 @clock

? 7 def fibonacci(n):

? 8? ? if n < 2:

? 9? ? ? ? return n

10? ? return fibonacci(n-2)+fibonacci(n-1)

11

12

13 if __name__ == '__main__':

14? ? print(fibonacci(6))

第5行:注釋funtools.lru_cache()

返回結(jié)果:

[0.00000046] fibonacci(0) -> 0

[0.00000053] fibonacci(1) -> 1

[0.00006782] fibonacci(2) -> 1

[0.00000030] fibonacci(1) -> 1

[0.00000035] fibonacci(0) -> 0

[0.00000037] fibonacci(1) -> 1

[0.00001312] fibonacci(2) -> 1

[0.00002514] fibonacci(3) -> 2

[0.00010535] fibonacci(4) -> 3

[0.00000030] fibonacci(1) -> 1

[0.00000030] fibonacci(0) -> 0

[0.00000037] fibonacci(1) -> 1

[0.00001209] fibonacci(2) -> 1

[0.00002376] fibonacci(3) -> 2

[0.00000028] fibonacci(0) -> 0

[0.00000038] fibonacci(1) -> 1

[0.00001210] fibonacci(2) -> 1

[0.00000028] fibonacci(1) -> 1

[0.00000036] fibonacci(0) -> 0

[0.00000034] fibonacci(1) -> 1

[0.00001281] fibonacci(2) -> 1

[0.00002466] fibonacci(3) -> 2

[0.00004897] fibonacci(4) -> 3

[0.00008414] fibonacci(5) -> 5

[0.00020196] fibonacci(6) -> 8

8

當(dāng)取消掉第5行注釋時(shí);

[0.00000040] fibonacci(0) -> 0

[0.00000049] fibonacci(1) -> 1

[0.00008032] fibonacci(2) -> 1

[0.00000066] fibonacci(3) -> 2

[0.00009398] fibonacci(4) -> 3

[0.00000063] fibonacci(5) -> 5

[0.00010943] fibonacci(6) -> 8

8

可以發(fā)現(xiàn)树瞭,lru_cache()這個(gè)裝飾器拇厢,極大的提高了計(jì)算性能;

maxsize 參數(shù)指定存儲(chǔ)多少個(gè)調(diào)用的結(jié)果晒喷。緩存滿了之后孝偎,舊的結(jié)果會(huì)被扔掉,騰出空間凉敲。為了得到最佳性能衣盾,maxsize 應(yīng)該設(shè)為 2 的 冪。typed 參數(shù)如果設(shè)為 True爷抓,把不同參數(shù)類型得到的結(jié)果分開保存势决,即把通常認(rèn)為相等的浮點(diǎn)數(shù)和整數(shù)參數(shù)(如 1 和 1.0)區(qū)分開。順 便說一下蓝撇,因?yàn)?lru_cache 使用字典存儲(chǔ)結(jié)果果复,而且鍵根據(jù)調(diào)用時(shí)傳 入的定位參數(shù)和關(guān)鍵字參數(shù)創(chuàng)建,所以被 lru_cache 裝飾的函數(shù)渤昌,它的所有參數(shù)都必須是可散列的虽抄。

5:多重裝飾器

@d1

@d2

def f():

? print('hello world')


###########################

def f():

? print("hello world")

f = d1(d2(f))

上下兩塊代碼是等效效果;

想要獲取更多Python學(xué)習(xí)資料可以加

QQ:2955637827私聊

或加Q群630390733

大家一起來學(xué)習(xí)討論吧耘沼!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末极颓,一起剝皮案震驚了整個(gè)濱河市朱盐,隨后出現(xiàn)的幾起案子群嗤,更是在濱河造成了極大的恐慌,老刑警劉巖兵琳,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狂秘,死亡現(xiàn)場離奇詭異,居然都是意外死亡躯肌,警方通過查閱死者的電腦和手機(jī)者春,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來清女,“玉大人钱烟,你說我怎么就攤上這事。” “怎么了拴袭?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵读第,是天一觀的道長。 經(jīng)常有香客問我拥刻,道長怜瞒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任般哼,我火速辦了婚禮吴汪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蒸眠。我一直安慰自己漾橙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布黔宛。 她就那樣靜靜地躺著近刘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臀晃。 梳的紋絲不亂的頭發(fā)上觉渴,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音徽惋,去河邊找鬼案淋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛险绘,可吹牛的內(nèi)容都是我干的踢京。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼宦棺,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼瓣距!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起代咸,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤蹈丸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后呐芥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逻杖,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年思瘟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荸百。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滨攻,死狀恐怖够话,靈堂內(nèi)的尸體忽然破棺而出蓝翰,到底是詐尸還是另有隱情,我是刑警寧澤女嘲,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布霎箍,位于F島的核電站,受9級(jí)特大地震影響澡为,放射性物質(zhì)發(fā)生泄漏漂坏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一媒至、第九天 我趴在偏房一處隱蔽的房頂上張望顶别。 院中可真熱鬧,春花似錦拒啰、人聲如沸驯绎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剩失。三九已至,卻和暖如春册着,著一層夾襖步出監(jiān)牢的瞬間拴孤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工甲捏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留演熟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓司顿,卻偏偏與公主長得像芒粹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子大溜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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