Blog Cover Image

Inspire you to have New thinking, Walk out your unique Road.

有的時候,你無意間遇到的一些故事,會激發你的靈感,改變你的想法,接下來你會用與之前全然不同的觀念去創造屬於你獨特的故事。

Sign @MinaYu.

[後端小菜鳥] Python Decorator 實作學習

Posted on

在約莫一兩個月前,我被賦予一個任務是利用Python Decorator做重構

當時我還不知道使用Decorator來解決問題,可以砍掉專案一半的代碼

Decorator是一個超棒的方法,但對於我來說它並不太好學習。

初次嘗試Decorator 是在今年的1月,我用了剛跑完Scurm 一個 Spring結束的喘息時間,嘗試學習Decorator,目的就是達成前輩開的最後一個Issue: 用Decorator重構專案。

我有看過前輩使用Decorator, 就像還在學日文50音的程度看著前輩們寫出一篇日文的文章,你看著前輩那些高超的程式碼,竟懷疑起自己到底能不能達到那個境界…

我東找找西找找就是沒辦法好好地寫出一個至少能動的decorator

而這篇,就是寫紀錄,我成功學習了Decorator來重構專案程式碼,並刪除至少一半的code

什麼是Decorator

Decorator就是裝飾器, 如下列程式碼,很像 裝飾 在函式上的裝飾器


@menu
def make_coffee():
    pass

@menu
def make_ice_tea():
    pass

在這個範例中 @menu 就是Decorator,我簡單用做餐點的邏輯寫一個範例程式

我學會了Decorator並運用在專案,這讓我砍掉我先前寫的一半的程式碼,但在文章中我不會秀出我在專案寫的程式碼,會用很相同的邏輯改寫。


前提有提及在剛開始找遍各大網站就是看不懂Decorator怎麼使用qq,但在功力提升的兩個月後,搭配找到的寶典,終於實作出我要的功能啦!!!

寶典在此,感謝大大的整理

來實作Decorator囉!

這個實作參考 有參數的 Decorator Function

再次強調這個參考實作來自OT Coding Note,有興趣可以點進去看

Note: 需要多一層的 outter function 來傳入參數
 def decorateFruit(fruit, rotLevel):
    def outer_d_f(f):
        def d_f(*args, **kargs):
            print("%s %s before call" % (rotLevel, fruit))
            result = f(*args, **kargs)
            print("%s %s after call" % (rotLevel, fruit))
            return result
        return d_f
    return outer_d_f

@decorateFruit('banana', 'new')
def print_hello2():
    print("hello 2nd time.")

@decorateFruit('guava', '50% rot')
def print_hello3():
    print("hello 3th time.")
> print_hello2()
> print('')
> print_hello3()
output
new banana before call
hello 2nd time.
new banana after call

50% rot guava before call
hello 3th time.
50% rot guava after call

改成自己想實現的功能

首先先介紹一下需求:

我在專案裡主要是運用來判斷眾資料庫欄位的 資料型態: object, float64 ..

所以如果有相關類似需求也可以拿來改寫!但由於是文章中就必須保密做改寫惹qq

需求描述:
假設我們開一個飲料店,做不同的飲料需要不同的材料(material)跟工具(tools)
每個飲料都會制定用哪些材料製作,製作的時候會檢查材料倉庫(storehouse)
若沒有足夠的材料可以製作飲料就不做,我要怎麼達成自動判斷呢?

我寫了一個範例如下,是改裝OT Coding Note有參數的 Decorator Function

# 這是我的Decorator
def auto_check_material(material):
    def outer_func(make):
        def check_material(*args):
            # 假設 storehouse 是list, 內有倉庫全部有的做菜材料
            storehouse, tools = args
            # 檢查倉庫裡有沒有材料
            if material not in storehouse:
                # 倉庫裡沒材料所以 False 不做
                return False
            # 反之倉庫有材料, 我們就傳入製作飲料的function
            return make(*args)
        return check_material
    return outer_func

# 裝飾Decorator的函式們, 會輸入製作這杯飲料要具備什麼材料
@auto_check_material(['coffee beans', 'water'])
def make_coffee(food_storehouse, tools):
    # 倉庫有材料才會做
    pass

@auto_check_material(['black tea bag', 'water'])
def make_ice_tea(food_storehouse, tools):
    # 倉庫有材料才會做
    pass

@auto_check_material(['black tea bag', 'apple', 'water'])
def make_apple_tea(food_storehouse, tools):
    # 倉庫有材料才會做
    pass

有些註解寫好了

我的Decorator 是 auto_check_material, 這個函式主要幫我自動確認倉庫內有無材料,傳入的變數為 製作某個飲料需要的材料

會有三層 function (但我現在還沒深入研究qq, 所以若講解怪怪的要自動訂正XD)

合理假設 outer_func(make) 就是被裝飾的函式們, 也就是說我在 check_material 確認材料有在倉庫後就會執行 return make(*args) ,確認可以製作飲料,就進到製作的函式開始製作飲料。

***講解時間***

我也是在打文章時才梳理清楚邏輯XD, 也就是說這些函式的執行是

當程式一執行,最先會經過三個製作飲料的函式

假設一開始run 遇到 make_coffee


@auto_check_material(['coffee beans', 'water'])
def make_coffee(food_storehouse, tools):
    pass

接著發現製作咖啡 make_coffee 的函式上有個裝飾器 auto_check_material, 代表前提是要先通過auto_check_material 合格後才能執行 make_coffee

裝飾器上帶有 (['coffee beans', 'water']),這是執行Decorator 需要用到的變數,在需求中代表著要有這兩個材料才能做咖啡。

進到 Decorator

# 這是我的Decorator
def auto_check_material(material):
    def outer_func(make):
        # 會先經過並執行check_material
        def check_material(*args):
            # 假設 storehouse 是list, 內有倉庫全部有的做菜材料
            storehouse, tools = args
            if material not in storehouse:
                return False
            # 若符合就進到outer_func也就是外部的 def make_coffee():
            return make(*args)
        return check_material
    return outer_func

因為進到裝飾器內,所以這邊的 outer_func 指的是在外層製作飲料的函式 make_coffee

意思是說在這個裝飾器內,前提要先通過 check_material 確認材料都有在 storehouse內,才將變數 *args 塞往下一步 > outer_func 外部函式 (也就是咱製作飲料的函式)


@auto_check_material(['coffee beans', 'water'])
def make_coffee(storehouse, tools):
    # 執行玩decorator後, 符合True 就做這個function
    # 變數 storehouse, tools 會一起送進Decorator 再回來這個函式中使用
    ...
    # 一連串製作後,生出咖啡,送出咖啡
    return coffee

執行完 Decorator » outer_func » 執行make_coffee 製作咖啡

總結順序

這邊釐清一下執行函式的順序

程式跑下去的順序依序是

>> def make_coffee >> @auto_check_material >>
check_material(*args) >> outer_func(make) >> def make_coffee(製作)

>> def make_ice_tea >> @auto_check_material >>
check_material(*args) >> outer_func(make) >> def make_ice_tea(製作)

>> def make_apple_tea >> @auto_check_material >>
check_material(*args) >> outer_func(make) >> def make_apple_tea(製作)

嗯!大概是這樣子

想當初, 真心覺得實作Decorator困難,因為是用上班時間學習 + 實作於專案

只要一整個下午都在研究這個,就覺得天啊我好像花了很多時間qq

但後來因為專案進度又開始跑了,才在當時學一半就沒繼續往下學

經過兩個月程式能力有稍微變強(?),也許是找到神人的文章XD

才順利地利用Decorator 重構專案程式!!! 真心超級讚啊qqqqqq

這也代表了我的程式能力又精進了一大步呢!!