第一次嘗試翻譯《Python裝飾器 II :裝飾器的引數》
第一次嘗試翻譯,由於本人水平有限,歡迎指正。
本文也是簡單 12 步理解 Python 裝飾器推薦繼續閱讀的兩篇文章之一。
原文地址:Python Decorators II: Decorator Arguments
原文標題:
Computing Thoughts
Python Decorators II: Decorator Arguments
by Bruce Eckel
October 19, 2008
以下是翻譯內容
摘要:當有引數傳遞給裝飾器時,裝飾器的執行機制是明顯不同於沒有引數傳遞的時候的
回顧:沒有引數傳遞時的裝飾器執行機制
在第一部分,我展示瞭如何使用沒有引數傳遞情況下的裝飾器,之所以我用先用“類”來定義裝飾器,是因為我覺得比較容易被理解。
如果我們生成一個沒有引數傳遞的裝飾器,那個需要被裝飾的函式就會被傳遞給裝飾器,每次裝飾器被觸發的時候(譯者加一句:就是每次被函式呼叫操作符“()”呼叫的時候),call
class decoratorWithoutArguments(object):
def __init__(self, f):
"""
If there are no decorator arguments, the function
to be decorated is passed to the constructor.
"""
print ("Inside __init__()")
self.f = f
def __call__(self, * args):
"""
The __call__ method is not called until the
decorated function is called.
"""
print ("Inside __call__()")
self.f(*args)
print ("After self.f(*args)")
@decoratorWithoutArguments #譯者加註 1
def sayHello(a1, a2, a3, a4):
print ('sayHello arguments:' , a1, a2, a3, a4)
print ("After decoration") #譯者加註 2
print ("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print ("After first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print ("After second sayHello() call")
任何被傳遞給裝飾器的引數都會被指派給__call__(),因此,輸出結果:
Inside __init__() #譯者加註 1的位置被呼叫,注意,沒有引數傳遞給裝飾器
After decoration #譯者加註 2的位置被呼叫
Preparing to call sayHello()
Inside __call__()
sayHello arguments: say hello argument list
After self.f(*args)
After first sayHello() call
Inside __call__()
sayHello arguments: a different set of arguments
After self.f(*args)
After second sayHello() call
請注意,在裝飾器被構建的時候(譯者注 1的位置),只有__init__()方法被呼叫,而__call__()方法是每次通過函式呼叫操作符“()”呼叫sayhello函式的時候才被呼叫。
有引數的裝飾器
現在修改上面的例子,看看有引數傳遞的裝飾器的工作機制。
class decoratorWithArguments(object):
def __init__(self, arg1, arg2, arg3):
"""
If there are decorator arguments, the function
to be decorated is not passed to the constructor!
"""
print ("Inside __init__()")
self.arg1 = arg1
self.arg2 = arg2
self.arg3 = arg3
def __call__(self, f):
"""
If there are decorator arguments, __call__() is only called
once, as part of the decoration process! You can only give
it a single argument, which is the function object.
"""
print ("Inside __call__()")
def wrapped_f(*args):
print ("Inside wrapped_f()")
print ("Decorator arguments:", self.arg1, self.arg2, self.arg3)
f(*args)
print ("After f(*args)")
return wrapped_f
@decoratorWithArguments("hello", "world", 42) #譯者加註 1
def sayHello(a1, a2, a3, a4):
print ('sayHello arguments:', a1, a2, a3, a4)
print ("After decoration")
print ("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print ("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print ("after second sayHello() call")
從輸出可以看出明顯的不同。
Inside __init__() #譯者加註 1 處呼叫執行
Inside __call__() #譯者加註 1 處呼叫執行
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call
裝飾器的呼叫流程發生了明顯的變化,當構建裝飾器的時候,首先呼叫構造器__init__(),然後立即呼叫__call__(),call()方法接收輸入的引數(即被裝飾函式sayHello),然後返回真正的裝飾函式wrapped_f。注意,call()方法只被呼叫這一次,而後,每次對裝飾器的呼叫,實際上都是呼叫那個裝飾函式warpped_f。
雖然看起來很容易理解,但還是要注意不同情況下編寫裝飾器類的區別。
帶引數的裝飾器函式
最後,學習更復雜的裝飾器函式的實現方法,這種實現方法,一次性的定義所有裝飾器所設計的內容。
def decoratorFunctionWithArguments(arg1, arg2, arg3):
def wrap(f):
print "Inside wrap()"
def wrapped_f(*args):
print "Inside wrapped_f()"
print "Decorator arguments:", arg1, arg2, arg3
f(*args)
print "After f(*args)"
return wrapped_f
return wrap
@decoratorFunctionWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
print 'sayHello arguments:', a1, a2, a3, a4
print "After decoration"
print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"
輸出結果如下:
Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call
裝飾器的返回值必須是一個“用於包裝被裝飾函式”的函式,也就是說,在每次“裝飾”的時候,python都會呼叫那個“用於包裝被包裝函式”的函式(wrap),並把“被裝飾函式”傳遞給它,這也正是我們有三層函式的原因:最內層的那個函式才是真正的那個“裝飾函式”。
因為python的“閉包”特性,wrap函式能夠訪問到裝飾器的引數arg1、arg2和arg3,而不必像“類裝飾器”中那樣,將引數儲存在類的屬性中。然而,根據“明確要比模糊強”這個概念,雖然函式版本的裝飾器定義更加簡明扼要,但還是類版本的裝飾器更加容易理解、修改及維護。