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 = funcdef __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_prefixdef __call__(self, func):
def wrapper(*args, **kwargs):
print(self.in_prefix)
func(*args, **kwargs)
print(self.out_prefix)
return wrapperdef 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()