[譯] Python裝飾器Part II:裝飾器參數

這是Python裝飾器講解的第二部分,上一篇:Python裝飾器Part I:裝飾器簡介

回顧:不帶參數的裝飾器

Python裝飾器Part I:裝飾器簡介中,我演示了怎么樣使用無參數的裝飾器,主要是使用類式裝飾器,因為這樣更容易理解。
如果我們創建了一個不帶參數的裝飾器,被裝飾的方法會傳遞給裝飾器的構造器,然后在被裝飾的函數被調用的時候,裝飾器的__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
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"

任何傳遞給被裝飾方法的參數都將傳遞給__call__(),輸出日志是:

Inside __init__()
After decoration
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

需要注意的是,在“裝飾”階段,只有__init__()會被調用;同時只有在被裝飾方法被調用的時候,__call__()才會被調用。

帶參數的裝飾器

現在我們把上面的那個例子簡單的改動一下,看看在添加裝飾器參數的情況下會發生什么情況:

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)
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__()
Inside __call__()
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

現在,在“裝飾”階段,構造器和__call__()都會被依次調用,__call__()也只接受一個函數對象類型的參數,而且必須返回一個裝飾方法去替換原有的方法,__call__()只會在“裝飾”階段被調用一次,接著返回的裝飾方法會被實際用在調用過程中。
盡管這個行為很合理,構造器現在被用來捕捉裝飾器的參數,而且__call__()不能再被當做裝飾方法,相反要利用它來完成裝飾的過程。盡管如此,第一次見到這種與不帶參數的裝飾器迥然不同的行為還是會讓人大吃一驚,而且它們的編程范式也有很大的不同。

帶參數的函數式裝飾器

最后,讓我們看一下更復雜的函數式裝飾器,在這里你不得不一次完成所有的事情:

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會在裝飾發生的時候拿到并且調用這個返回的函數結果,然后傳遞給被裝飾的函數,這就是為什么我們在裝飾器的實現里嵌套定義了三層的函數,最里層的那個函數是新的替換函數。
因為閉包的特性, wrapped_f()在不需要像在類式裝飾器例子中一樣顯示存儲arg1, arg2, arg3這些值的情況下,就能夠訪問這些參數。不過,這恰巧是我覺得“顯式比隱式更好”的例子。盡管函數式裝飾器可能更加精簡一點,但類式裝飾器會更加容易理解并因此更容易被修改和維護。

原文地址:Python Decorators II: Decorator Arguments

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容