![](https://static.wixstatic.com/media/29c510_89e54ff478104a998a89fe8b065c0106~mv2.jpg/v1/fill/w_980,h_854,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_89e54ff478104a998a89fe8b065c0106~mv2.jpg)
想像一下在萬聖節的時候,我們在門上,桌上,牆上,傢俱等各種物品上放上南瓜妖怪🎃的裝飾品。
藉由這些裝飾品,原本平凡的門跟傢俱,在不需要改造它們本質的情況下,輕輕鬆鬆就增添了萬聖節的氣氛。甚至,我可以想裝飾哪裡就哪裡,就算是廁所🚽也沒問題。
如果,今天沒有這些萬聖節裝飾品,當我想要營造這個氣氛時,我搞不好得叫木工在牆上做個造型,或是把門的樣式都改掉,不用說廁所的難度又更高了。
聽起來很不符合經濟效益,是吧?所以裝飾品是很好用的 👍🏻
在Python中,有一個類似的概念,叫做Decorator。
它能讓我們去對指定的function增添一些東西,甚至是改變它的行為,而不需要去動到function本身。
聽起來好像有點神奇嗎?那麼,我們來看一個簡單的例子吧。
假設我有下面三個簡單的function:
![](https://static.wixstatic.com/media/29c510_1ee68e6026574999a1e0de53d53440b2~mv2.png/v1/fill/w_980,h_269,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_1ee68e6026574999a1e0de53d53440b2~mv2.png)
這三個function分別return不同的字串:hello、hi、yo
今天,如果說我想要每個function的return值都加上一個南瓜的表情符號,像這樣:
>>>hello 🎃
>>>hi 🎃
>>>yo 🎃
我可以怎麼做呢?
最直覺的方式,當然我可以直接去改這些function (是的,你可以直接在code上面用表情符號😋):
![](https://static.wixstatic.com/media/29c510_5ee92eb64ba34cdd9ad50ed07c665d1d~mv2.png/v1/fill/w_980,h_270,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_5ee92eb64ba34cdd9ad50ed07c665d1d~mv2.png)
嗯,結果當然是OK。但是如果後來我決定我想要在加一個鬼鬼的符號👻,我就得每一個再去改一次,對吧?
![](https://static.wixstatic.com/media/29c510_8a6d478d1ba54ca2ad080dc77f762fee~mv2.png/v1/fill/w_980,h_290,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_8a6d478d1ba54ca2ad080dc77f762fee~mv2.png)
嗯...有點麻煩,而且好不容易做好的function,其實最好可以不要去動它。有什麼更好的方法來完成這件事嗎?
✨✨✨這裡就是decorator的登場啦!
Decorator function
我們首先來定義一個function,就叫他trick_or_treat吧:
![](https://static.wixstatic.com/media/29c510_551ac1345d464a718bc8ff50006c53ed~mv2.png/v1/fill/w_980,h_254,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_551ac1345d464a718bc8ff50006c53ed~mv2.png)
從結論來說,這個叫做trick_or_treat的function就是一個Python decorator!能夠幫我們之前的那些function的產出加上一個南瓜符號喔。
那怎麼用呢?
很簡單,用它包住我們原來的function,像這樣:
![](https://static.wixstatic.com/media/29c510_7b2f94b9c86049e9baf6cc2d53940ad0~mv2.png/v1/fill/w_980,h_57,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_7b2f94b9c86049e9baf6cc2d53940ad0~mv2.png)
注意到這裡我是讓say_hello這個function被重新定義。藉此,我們可以繼續利用say_hello這個variable name。
現在,當我們去執行say_hello的話,結果就如預期的加上了一個南瓜符號!
![](https://static.wixstatic.com/media/29c510_3dec12878e3545db8d790f7dfbc65d74~mv2.png/v1/fill/w_980,h_103,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_3dec12878e3545db8d790f7dfbc65d74~mv2.png)
當然,其他的function也可以如法炮製:
![](https://static.wixstatic.com/media/29c510_a422940fc3d1470fb6606e73e6f9ecad~mv2.png/v1/fill/w_980,h_182,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_a422940fc3d1470fb6606e73e6f9ecad~mv2.png)
-----------------------------------------------------------------------------------------------------------------------
OK,那具體說明一下trick_or_treat這個decorator function到底做了什麼:
trick_or_treat需要的argument是一個function物件。(在Python中,function也是一個物件,可以被當作argument被傳送)
在tricker_or_treat裡面,我們做(定義)了另外一個function,叫做new_func。這種在function裡面的function被稱作inner function(相對外面的就叫做outer function)。
這個new_func會call你傳過去的function,並且在回傳數值上加上一個南瓜符號🎃。
trick_or_treat最後會return這個new_func(注意return的是一個function物件,並沒有call這個function喔)。
嗯,有點饒舌...這裡重要的理解點就是,我們可以將function物件當作argument傳送,也可以作為return的產出 。這種特性叫做first class citizen。
利用這個特性,decorator才有辦法成立。
用小老鼠符號@來執行decorator
我們現在知道了何謂decorator。並且剛剛說到它的使用方式是像👇:
say_hello = trick_or_treat(say_hello)
其實,Python特別提供一個表現方式來取代上面的寫法。
我們可以利用小老鼠符號@放在你要裝飾的function上,像這樣:
![](https://static.wixstatic.com/media/29c510_b641b14308154dcb85b0cf2dfe77df06~mv2.png/v1/fill/w_980,h_106,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_b641b14308154dcb85b0cf2dfe77df06~mv2.png)
現在如果我們去執行say_hello,就會得到我們要的結果喔~
![](https://static.wixstatic.com/media/29c510_2a7ecf1ea2ab4859a44c4a23bca5722a~mv2.png/v1/fill/w_980,h_110,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_2a7ecf1ea2ab4859a44c4a23bca5722a~mv2.png)
利用小老鼠@套上我們要裝飾的function,這是Python常見的decorator的呈現樣貌。
看起來是不是更符合decorator -- 🌼 裝飾的感覺🌼
利用同樣的方式,我們可以裝飾其他的function:
![](https://static.wixstatic.com/media/29c510_d4f05128e6904f1e955bb02723414d3b~mv2.png/v1/fill/w_980,h_316,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_d4f05128e6904f1e955bb02723414d3b~mv2.png)
視覺上,這些function們是不是更有美感跟一致性了~🌼
利用decorator測試function需要的計算時間
OK,現在我們知道了小老鼠@的用法,接下來看一個比較實用的decorator的例子吧。
下面的print_time是一個decorator,可以用來測試一個function所花費的時間。
![](https://static.wixstatic.com/media/29c510_6bebc67e8ff049c89cbe502d8d17b206~mv2.png/v1/fill/w_980,h_246,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_6bebc67e8ff049c89cbe502d8d17b206~mv2.png)
幾個注記:
inner_func中的*args和**kwargs,簡單來說能讓我們能夠放入任何數量的argument。所以不管你要裝飾的function需要幾個arguments,print_time這個decorator都可以應對。這是decorator中我們常會用到的作法。
這個inner function習慣上我們也會叫他做wrapper,因為它是實際上包住我們要裝飾的function的部分。
下面是使用例,我們將print_time分別用在some_func0,some_func1,以及some_func2。這幾個function基本上做的事是一樣(跑一個loop,做一個list)的,只是寫法不同:
![](https://static.wixstatic.com/media/29c510_e8c4392ad74f48baba65ba26275155a3~mv2.png/v1/fill/w_980,h_318,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_e8c4392ad74f48baba65ba26275155a3~mv2.png)
來run一下看看~
![](https://static.wixstatic.com/media/29c510_03c8841ccacb4a128980d2418f79bf29~mv2.png/v1/fill/w_980,h_290,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_03c8841ccacb4a128980d2418f79bf29~mv2.png)
看起來,some_func1似乎是這幾個function中效率最好的🐎。
所以這個decorator是不是蠻有用的?簡單的放在任何一個function,就能夠讓我們測試哪一種寫法在Python中跑的比較快! 😀
Closure
在看過了decorator的使用方法跟例子後,現在我們應該有比較清楚的概念了吧。但是,其實decorator還有一個重要的特性,這裡也想跟大家稍微一起看一下,幫助我們更加理解。
剛剛說到,當我們定義一個decorator的時候,我們在裡面會做一個inner function並return它,對吧?
這個inner function所存在的小小世界,我們稱之為enclosing scope。
![](https://static.wixstatic.com/media/29c510_517eb9e76be64da1896859db696b11e0~mv2.png/v1/fill/w_980,h_242,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_517eb9e76be64da1896859db696b11e0~mv2.png)
重點來了,在這個小小世界中所出現的variables,inner function不但可以去使用,而且當inner function被return之後,它還會繼續被保存下來!
上面的trick_or_treat例子中,我們看到的func這個variable就是這樣。
![](https://static.wixstatic.com/media/29c510_648d53e9afce4b28a5ba234ce99b6472~mv2.png/v1/fill/w_980,h_255,al_c,q_85,usm_0.66_1.00_0.01,enc_auto/29c510_648d53e9afce4b28a5ba234ce99b6472~mv2.png)
即便在我們return這個new_func之後,它仍然記得這一個func變數(所以我們不至於拿到一個NameError跟你說func未被定義)。
因此,其實我們還可以這麼做...將剛剛的trick_or_treat改造一下:
![](https://static.wixstatic.com/media/29c510_4b5a25bc8de2432db863cd1e5449fda4~mv2.png/v1/fill/w_980,h_492,al_c,q_90,usm_0.66_1.00_0.01,enc_auto/29c510_4b5a25bc8de2432db863cd1e5449fda4~mv2.png)
這個code的重點:
在new_func的外面定義了兩個variable,一個是emoji,一個是emoji_list。因為我們知道inner function可以記住這些variable。
接著,每次只要call一次被裝飾的function,就會加一個南瓜到emoji_list裡面
結果就是,被裝飾的function被call了幾次,return的值就會有幾個南瓜!
![](https://static.wixstatic.com/media/29c510_6463eaf7bb204f939f0380f60ad13bf6~mv2.png/v1/fill/w_980,h_371,al_c,q_90,usm_0.66_1.00_0.01,enc_auto/29c510_6463eaf7bb204f939f0380f60ad13bf6~mv2.png)
這樣的一個概念叫做closure。就像是創造了一個關閉的小空間,外面的世界無法直接接觸,而在這個空間裡面,有它自己的生態得以被保留 - 彷彿電影侏羅紀公園一樣。
透過closure,可以做到許多蠻酷的事,譬如說像上面一樣追蹤function被使用的次數,快取記憶(cache)的存取或是記錄log等等...。
小結
今天我們討論了:
Python decorator的基本概念以及為什麼要用它
如何做出decorator
用小老鼠@來使用decorator
decorator的實用例子
何謂closure
Decorator的應用範圍相當廣,尤其在使用其他Python工具套件時經常會出現。之前我們曾經介紹過的第三方的library:tenacity也是應用decorator來達成的。
因此,知道如何使用它,以及了解為什麼會有decorator的存在是在學習Python中蠻重要的一個環節,希望有幫助到你的學習喔~😀
Comments