創作內容

0 GP

python幾種裝飾器的寫法

作者:已經改掉暱稱的米奇│2024-01-14 03:42:21│巴幣:0│人氣:65
1. 基本版
沒什麼特別的,老師第一堂課就會教
def decorator(func):
    def inner(*args, **kwargs):
        print('foo')
        ret = func(*args, **kwargs)
        print('bar')
        return ret
    return inner


@decorator
def func(a, b):
    return a + b

print(func(1, 2))   

# foo
# bar
# 3


2. 寫成class版本的裝飾器
看過一些文章順便教怎麼用class寫裝飾器
但我覺得不是很推薦
class decorator:
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        print('foo')
        ret = self.func(*args, **kwargs)
        print('bar')
        return ret

@decorator
def func(a, b):
    return a + b

print(func(1, 2))

# foo
# bar
# 3

這個寫法拿去修飾類別method的話會出現問題
class decorator:
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        print('foo')
        ret = self.func(*args, **kwargs)
        print('bar')
        return ret

class Test:
    @decorator
    def func(self, a, b):
        return a + b
        
print(Test().func(1, 2))

# foo
# TypeError: func() missing 1 required positional argument: 'b'

原因是呼叫 func 時,通常 python 會幫你自動將 instance 代入第一個參數
但前提是 func 必須要是一個 method 才行
如果用這方法定義裝飾器,那麼 func 不再是一個函式了,變成是 Test 底下的一個 instance,其類別為 decorator
既然 func 不是 Test 的 method 了,那麼 python 就不會幫你帶入 instance,因此呼叫時就少了一個參數


3. 帶參數的裝飾器
此時使用的東西,與其說是裝飾器,不如說是一個裝飾器工廠
因此參數帶給裝飾器工廠,工廠產生對應的裝飾器,該裝飾器再去修飾函式
def decorator_factory(msg1='foo', msg2='bar'):
    def decorator(func):
        def inner(*args, **kwargs):
            print(msg1)
            ret = func(*args, **kwargs)
            print(msg2)
            return ret
        return inner
    return decorator

@decorator_factory('not foo', 'not bar')
def func(a, b):
    return a + b

# 相當於
# decorator = decorator_factor('not foo', 'not bar')
# func = decorator(func)

print(func(1, 2))

# not foo
# not bar
# 3


4. 同時支援帶參數與不帶參數的裝飾器
像是 dataclass 這樣,可以寫成
@dataclass
class Something: ...
也可以寫成
@dataclass(frozen=True)
class Something: ...

要讓裝飾器同時支援帶參數與不帶參數,需要由裝飾器工廠判斷當下是否有帶參數
先從裝飾器的行為來看會比較清楚
沒有帶參數的情況:
@decorator_factory
def func(): ...

# 等於

func = decorator_factory(func)

有帶參數的情況:
@decorator_factory(msg1='not foo', msg2='not bar')
def func(): ...

# 等於

func = decorator_factory(msg1='not foo', msg2='not bar')(func)

沒帶參數時,是直接把 func 傳入裝飾器工廠
有帶參數時,是先把參數傳給裝飾器工廠,工廠產生裝飾器,再去修飾 func

因此裝飾器工廠會需要判斷傳入的是一個函式 (沒帶參數的情況) 或是參數 (有帶參數的情況)
程式碼如下
沒帶參數時,func 會傳入裝飾器工廠,也就是 decoratee 不為 None,此時工廠將 func 代入裝飾器,其餘參數 msg1、msg2 使用預設值,接著回傳出去
有帶參數時,func 沒有傳入裝飾器工廠,所以 decoratee 為 None,因此工廠只需單純將裝飾器回傳即可

def decorator_factory(decoratee=None, msg1='foo', msg2='bar'):
    def decorator(func):
        def inner(*args, **kwargs):
            print(msg1)
            ret = func(*args, **kwargs)
            print(msg2)
            return ret
        return inner
            
    if decoratee is not None:  # decorator without argument
        return decorator(decoratee)
    else:                      # decorator with argument
        return decorator
        
@decorator_factory
def func(a, b):
    return a + b

print(func(1, 2))

# foo
# bar
# 3


@decorator_factory(msg1='not foo', msg2='not bar')
def func(a, b):
    return a + b
    
print(func(1, 2))

# not foo
# not bar
# 3


5. 能夠存取類別內其他東西的裝飾器
假設現在希望裝飾器能夠取得類別內的其他東西,類似這樣的效果
class Test:
    X = 100
    
    @decorator
    def func(self, a, b):
        return a + b    # 裝飾器讓這裡的回傳值再乘上self.X

作法其實跟一般的裝飾器一模一樣
只是要自己把 *args 的第一個參數拿出來,作為 self 使用

def decorator(func):
    def inner(*args, **kwargs):
        self_ = args[0]
        args = args[1:]
        print('foo')
        ret = func(self_, *args, **kwargs)
        print('bar')
        return ret * self_.X
    return inner
    
class Test:
    X = 100
    
    @decorator
    def func(self, a, b):
        return a + b
        
print(Test().func(1, 2))

# foo
# bar
# 300


6. 突然忘記還有什麼例子了,想到再補充
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=5864698
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 0 篇留言

我要留言提醒:您尚未登入,請先登入再留言

喜歡★micky85lu 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:在C++實作python... 後一篇:工作2年心得...

追蹤私訊切換新版閱覽

作品資料夾

cindy8611324大家
小屋更新新立繪歐~看更多我要大聲說昨天10:26


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】