這是Python裝飾器講解的第二部分,上一篇:Python裝飾器Part I:裝飾器簡介
回顧:不帶參數(shù)的裝飾器
在Python裝飾器Part I:裝飾器簡介中,我演示了怎么樣使用無參數(shù)的裝飾器,主要是使用類式裝飾器,因?yàn)檫@樣更容易理解。
如果我們創(chuàng)建了一個(gè)不帶參數(shù)的裝飾器,被裝飾的方法會(huì)傳遞給裝飾器的構(gòu)造器,然后在被裝飾的函數(shù)被調(diào)用的時(shí)候,裝飾器的__call__()方法就會(huì)執(zhí)行。
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"
任何傳遞給被裝飾方法的參數(shù)都將傳遞給__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__()會(huì)被調(diào)用;同時(shí)只有在被裝飾方法被調(diào)用的時(shí)候,__call__()才會(huì)被調(diào)用。
帶參數(shù)的裝飾器
現(xiàn)在我們把上面的那個(gè)例子簡單的改動(dòng)一下,看看在添加裝飾器參數(shù)的情況下會(huì)發(fā)生什么情況:
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"
從輸出結(jié)果來看,運(yùn)行的效果發(fā)生了明顯的變化:
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
現(xiàn)在,在“裝飾”階段,構(gòu)造器和__call__()都會(huì)被依次調(diào)用,__call__()也只接受一個(gè)函數(shù)對象類型的參數(shù),而且必須返回一個(gè)裝飾方法去替換原有的方法,__call__()只會(huì)在“裝飾”階段被調(diào)用一次,接著返回的裝飾方法會(huì)被實(shí)際用在調(diào)用過程中。
盡管這個(gè)行為很合理,構(gòu)造器現(xiàn)在被用來捕捉裝飾器的參數(shù),而且__call__()不能再被當(dāng)做裝飾方法,相反要利用它來完成裝飾的過程。盡管如此,第一次見到這種與不帶參數(shù)的裝飾器迥然不同的行為還是會(huì)讓人大吃一驚,而且它們的編程范式也有很大的不同。
帶參數(shù)的函數(shù)式裝飾器
最后,讓我們看一下更復(fù)雜的函數(shù)式裝飾器,在這里你不得不一次完成所有的事情:
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"
輸出結(jié)果:
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
函數(shù)式裝飾器的返回值必須是一個(gè)函數(shù),能包裝原有被包裝函數(shù)。也就是說,Python會(huì)在裝飾發(fā)生的時(shí)候拿到并且調(diào)用這個(gè)返回的函數(shù)結(jié)果,然后傳遞給被裝飾的函數(shù),這就是為什么我們在裝飾器的實(shí)現(xiàn)里嵌套定義了三層的函數(shù),最里層的那個(gè)函數(shù)是新的替換函數(shù)。
因?yàn)?code>閉包的特性, wrapped_f()在不需要像在類式裝飾器例子中一樣顯示存儲(chǔ)arg1, arg2, arg3這些值的情況下,就能夠訪問這些參數(shù)。不過,這恰巧是我覺得“顯式比隱式更好”的例子。盡管函數(shù)式裝飾器可能更加精簡一點(diǎn),但類式裝飾器會(huì)更加容易理解并因此更容易被修改和維護(hù)。