Python: 用 class 來實作 decorator

Serge Lu
4 min readAug 28, 2018

--

Python 中的 decorator 除了可以用 function 來實作之外,也可以使用 class 加上實作 __init____call__ 這兩個 underscore methods 來實現,例如

class Logger(object):    def __init__(self, func):        self.func = func    def __call__(self, *args, **kwargs):        print('=>')        self.func(*args, **kwargs)        print('<=')

然後就可以像這樣使用它

@Logger
def f(a, b):
print('calling f with args ', (a, b,))
f(1, 2)

輸出會是

=>
calling f with args (1, 2)
<=

但如果想讓 decorator 可以傳入參數的話,實作的方法略有不同,範例如下

class CustomLogger(object):
def __init__(self, in_prefix, out_prefix):
self.in_prefix = in_prefix
self.out_prefix = out_prefix
def __call__(self, func):
def wrapper(*args, **kwargs):
print(self.in_prefix)
func(*args, **kwargs)
print(self.out_prefix)
return wrapper

用法

@CustomLogger('->', '<-')
def g(a, b):
print('calling g with args ', (a, b,))
g(2, 4)

輸出

->
calling g with args (2, 4)
<-

在無參數版本的實作中,被包裝的函數會在 __init__ 時被傳入,然後存在 self.func 中,然後在 __call__ 中調用。而帶參數版本的,是先將 decorator 需要的參數在 __init__ 時傳入並記下,然後在 __call__ 中產生另外一個 wrapper function 回傳。這不同可以從 decorator 的語法中略窺一二:

無參數版本

@Logger
def f():
pass

Logger 的 __init__直接吃 f 進來,然後回傳一個裝飾過的 f,所以 f 是在 __init__ 時傳入。

有參數版本

@CustomLogger('->', '<-')
def g():
pass

CustomLogger 是 __init__先吃 <- , -> 進來,然後再調用 CustomLogger 的 __call__ 來產生裝飾過的 g。

附上完整可執行的 code:

class Logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('=>')
self.func(*args, **kwargs)
print('<=')
class CustomLogger(object):
def __init__(self, in_prefix, out_prefix):
self.in_prefix = in_prefix
self.out_prefix = out_prefix
def __call__(self, func):
def wrapper(*args, **kwargs):
print(self.in_prefix)
func(*args, **kwargs)
print(self.out_prefix)
return wrapper
def main():
@Logger
def f(a, b):
print('calling f with args ', (a, b,))
f(1, 2) @CustomLogger('->', '<-')
def g(a, b):
print('calling g with args ', (a, b,))
g(2, 4)if __name__ == '__main__':
main()

--

--