top of page
Search
Learn with Shin

Decorator - 點綴我們的function



想像一下在萬聖節的時候,我們在門上,桌上,牆上,傢俱等各種物品上放上南瓜妖怪🎃的裝飾品。


藉由這些裝飾品,原本平凡的門跟傢俱,在不需要改造它們本質的情況下,輕輕鬆鬆就增添了萬聖節的氣氛。甚至,我可以想裝飾哪裡就哪裡,就算是廁所🚽也沒問題。


如果,今天沒有這些萬聖節裝飾品,當我想要營造這個氣氛時,我搞不好得叫木工在牆上做個造型,或是把門的樣式都改掉,不用說廁所的難度又更高了。


聽起來很不符合經濟效益,是吧?所以裝飾品是很好用的 👍🏻


在Python中,有一個類似的概念,叫做Decorator


它能讓我們去對指定的function增添一些東西,甚至是改變它的行為,而不需要去動到function本身。


聽起來好像有點神奇嗎?那麼,我們來看一個簡單的例子吧。


假設我有下面三個簡單的function:

這三個function分別return不同的字串:hello、hi、yo


今天,如果說我想要每個function的return值都加上一個南瓜的表情符號,像這樣:


>>>hello 🎃
>>>hi 🎃
>>>yo 🎃

我可以怎麼做呢?


最直覺的方式,當然我可以直接去改這些function (是的,你可以直接在code上面用表情符號😋):


嗯,結果當然是OK。但是如果後來我決定我想要在加一個鬼鬼的符號👻,我就得每一個再去改一次,對吧?



嗯...有點麻煩,而且好不容易做好的function,其實最好可以不要去動它。有什麼更好的方法來完成這件事嗎?


✨✨✨這裡就是decorator的登場啦!



Decorator function


我們首先來定義一個function,就叫他trick_or_treat吧:


從結論來說,這個叫做trick_or_treat的function就是一個Python decorator!能夠幫我們之前的那些function的產出加上一個南瓜符號喔。


那怎麼用呢?


很簡單,用它包住我們原來的function,像這樣:


注意到這裡我是讓say_hello這個function被重新定義。藉此,我們可以繼續利用say_hello這個variable name。


現在,當我們去執行say_hello的話,結果就如預期的加上了一個南瓜符號!



當然,其他的function也可以如法炮製:

-----------------------------------------------------------------------------------------------------------------------


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上,像這樣:


現在如果我們去執行say_hello,就會得到我們要的結果喔~



利用小老鼠@套上我們要裝飾的function,這是Python常見的decorator的呈現樣貌。

看起來是不是更符合decorator -- 🌼 裝飾的感覺🌼


利用同樣的方式,我們可以裝飾其他的function:

視覺上,這些function們是不是更有美感跟一致性了~🌼



利用decorator測試function需要的計算時間


OK,現在我們知道了小老鼠@的用法,接下來看一個比較實用的decorator的例子吧。


下面的print_time是一個decorator,可以用來測試一個function所花費的時間。

幾個注記:

  • 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)的,只是寫法不同:



來run一下看看~



看起來,some_func1似乎是這幾個function中效率最好的🐎。

所以這個decorator是不是蠻有用的?簡單的放在任何一個function,就能夠讓我們測試哪一種寫法在Python中跑的比較快! 😀



Closure


在看過了decorator的使用方法跟例子後,現在我們應該有比較清楚的概念了吧。但是,其實decorator還有一個重要的特性,這裡也想跟大家稍微一起看一下,幫助我們更加理解。


剛剛說到,當我們定義一個decorator的時候,我們在裡面會做一個inner function並return它,對吧?


這個inner function所存在的小小世界,我們稱之為enclosing scope。


重點來了,在這個小小世界中所出現的variables,inner function不但可以去使用,而且當inner function被return之後,它還會繼續被保存下來!


上面的trick_or_treat例子中,我們看到的func這個variable就是這樣。

即便在我們return這個new_func之後,它仍然記得這一個func變數(所以我們不至於拿到一個NameError跟你說func未被定義)。


因此,其實我們還可以這麼做...將剛剛的trick_or_treat改造一下:

這個code的重點:

  • 在new_func的外面定義了兩個variable,一個是emoji,一個是emoji_list。因為我們知道inner function可以記住這些variable。

  • 接著,每次只要call一次被裝飾的function,就會加一個南瓜到emoji_list裡面

  • 結果就是,被裝飾的function被call了幾次,return的值就會有幾個南瓜!



這樣的一個概念叫做closure。就像是創造了一個關閉的小空間,外面的世界無法直接接觸,而在這個空間裡面,有它自己的生態得以被保留 - 彷彿電影侏羅紀公園一樣。

透過closure,可以做到許多蠻酷的事,譬如說像上面一樣追蹤function被使用的次數,快取記憶(cache)的存取或是記錄log等等...。



小結


今天我們討論了:

  • Python decorator的基本概念以及為什麼要用它

  • 如何做出decorator

  • 用小老鼠@來使用decorator

  • decorator的實用例子

  • 何謂closure

Decorator的應用範圍相當廣,尤其在使用其他Python工具套件時經常會出現。之前我們曾經介紹過的第三方的library:tenacity也是應用decorator來達成的。


因此,知道如何使用它,以及了解為什麼會有decorator的存在是在學習Python中蠻重要的一個環節,希望有幫助到你的學習喔~😀


Comments


Post: Blog2_Post
bottom of page