Python – Class Decorator & Wrap

Loading

測試環境為 CentOS 8 x86_64 (虛擬機)

關於 Syntactic sugar 請參考 – https://zh.wikipedia.org/zh-tw/%E8%AF%AD%E6%B3%95%E7%B3%96

語法糖(英語:Syntactic sugar)是由英國電腦科學家彼得·蘭丁發明的一個術語,指電腦語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程式設計師使用。語法糖讓程式更加簡潔,有更高的可讀性。

在 Python 的 syntax candy(syntactic sugar) 被稱作 Decorator (使用 @ 當做 syntax candy),這邊來看一下 Class 的 Decorator(裝飾器) 與 wrapper 怎麼來使用 ,可以先從 Function 的 Decorator 來了解 – https://benjr.tw/104428

這邊看一下 使用 Class 的方法來寫裝飾詞 (有分有無參數) ,參考範例 – https://www.maxlist.xyz/2019/12/07/python-decorator/

  • 範例 : 無參數
    [root@localhost ~]# vi classdecorator1.py
    import time
    from functools import wraps
    
    class Timer():
        def __init__(self, func):
            self.func = func
    
        def __call__(self , *args , **kwargs):
                t_start = time.time()
                value = self.func(*args, **kwargs)
                t_end = time.time()
                t_count = t_end - t_start
                print('[Time]', t_count)
    
    @Timer
    def dosomethingClass(a, b):
        print('do some thing')
        print('a + b = ', a + b)
    
    dosomethingClass(1, 2)
    

    執行結果

    [root@localhost ~]# python3 classdecorator1.py
    do some thing
    a + b =  3
    [Time] 3.886222839355469e-05
    

    說明:
    匯入時間模組.

    import time
    

    後面使用到函數 time.time()- 預設回傳從 1970-01-0100:00:00 的秒數(無符號整數)到目前的時間差.

          t_start = time.time()
          t_end = time.time()
    

    在無參數版本的 decorator, 需在 __init__ 時傳入被包裝的函數,我們需自訂函數名稱 self.func 並儲存起來,之後可以在 __call__ 中使用.

        def __init__(self, func):
            self.func = func
    

    在 __call__ 裡面有使用到 self.func 就是指 dosomethingClass 這個函數,這邊還使用到 *args , **kwargs 可以接受任意引數 – https://benjr.tw/104484 .

        def __call__(self , *args , **kwargs):
                value = self.func(*args, **kwargs)
    

    其中的 @ 就是 syntax sugar.

    @Timer
    def dosomethingClass(a, b):
    

    上面步驟等同下面, Timer Class 會把 dosomethingClass 涵式檔做輸入參數,並把回傳的 class 命明為同名的 dosomethingClass .

    dosomethingClass = Timer(dosomethingClass(a, b))
    
  • 範例 : 有參數
    [root@localhost ~]# vi classdecorator2.py
    import time
    from functools import wraps
    
    class Timer:
        def __init__(self, time_sleep):
            print('[__init__]')
            print('[time_sleep]:', time_sleep)
    
            self.time_sleep = time_sleep
    
        def __call__(self, func):
            @wraps(func)
            def wrap(*args, **kwargs):
                t_start = time.time()
                time.sleep(self.time_sleep)
                value = func(*args, **kwargs)
                t_end = time.time()
                t_count = t_end - t_start
                print('[Time]', t_count)
                return value
            return wrap
    
    @Timer(time_sleep=3)
    def dosomethingClass(a, b):
        print('do some thing')
        print('a + b = ', a + b)
    
    dosomethingClass(1, 2)
    

    執行結果

    [root@localhost ~]# python3 classdecorator2.py
    [__init__]
    [time_sleep]: 3
    do some thing
    a + b =  3
    [Time] 3.0035414695739746
    

    說明:
    有參數時,須在 __init__ 把 decorator 需要的參數傳入並記錄下來,之後在 __call__ 透過 wrapper function 回傳.

        def __init__(self, time_sleep):
            print('[__init__]')
            print('[time_sleep]:', time_sleep)
    
            self.time_sleep = time_sleep
    

    從 functools 匯入 wraps , 避免 wrap 之後 __name__ 屬性就會變成 wrap 這個問題.

    from functools import wraps
    
            @wraps(func)
    
            return wrap
    

    定義了一個名稱為 Timer 的 Class

    class Timer:
    

    __init__ 會在 Instance 建立時執行.

        def __init__(self, time_sleep):
            print('[__init__]')
            print('[time_sleep]:', time_sleep)
    
            self.time_sleep = time_sleep
    

    __call__ (請參考 – https://benjr.tw/104454 ) – 當 Instance 作為函數時使用就會去呼叫 __call__ 涵式.裡面透過 wrap (包裝)另一個函數以擴展函數.

        def __call__(self, func):
            @wraps(func)
            def wrap(*args, **kargs):
                t_start = time.time()
                time.sleep(self.time_sleep)
                value = func(*args, **kargs)
                t_end = time.time()
                t_count = t_end - t_start
                print('[Time]', t_count)
                return value
            return wrap
    

    自訂一個函數 dosomething 並 透過 @ 語法糖 (syntax sugar) ,在呼叫 dosomething 去產生 class Timer 的 Instance .

    @Timer(time_sleep=3)
    def dosomethingClass(a, b):
        print('do some thing')
        print('a + b = ', a + b)
    

    呼叫 dosomething 函數.

    dosomethingClass(1, 2)
    
沒有解決問題,試試搜尋本站其他內容

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料