這是Python裝飾器講解的第二部分,上一篇:Python裝飾器Part I:裝飾器簡介
回顧:不帶參數(shù)的裝飾器
在Python裝飾器Part I:裝飾器簡介中,我演示了怎么樣使用無參數(shù)的裝飾器协怒,主要是使用類式裝飾器
卑笨,因為這樣更容易理解赤兴。
如果我們創(chuàng)建了一個不帶參數(shù)的裝飾器妖滔,被裝飾的方法會傳遞給裝飾器的構(gòu)造器桶良,然后在被裝飾的函數(shù)被調(diào)用的時候铛楣,裝飾器的__call__()
方法就會執(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__()
會被調(diào)用;同時只有在被裝飾方法被調(diào)用的時候瑰步,__call__()
才會被調(diào)用矢洲。
帶參數(shù)的裝飾器
現(xiàn)在我們把上面的那個例子簡單的改動一下,看看在添加裝飾器參數(shù)的情況下會發(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é)果來看缩焦,運行的效果發(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__()
都會被依次調(diào)用,__call__()
也只接受一個函數(shù)對象類型的參數(shù)揩徊,而且必須返回一個裝飾方法去替換原有的方法塑荒,__call__()
只會在“裝飾”階段被調(diào)用一次,接著返回的裝飾方法會被實際用在調(diào)用過程中齿税。
盡管這個行為很合理凌箕,構(gòu)造器現(xiàn)在被用來捕捉裝飾器的參數(shù),而且__call__()
不能再被當做裝飾方法陌知,相反要利用它來完成裝飾的過程他托。盡管如此,第一次見到這種與不帶參數(shù)的裝飾器迥然不同的行為還是會讓人大吃一驚仆葡,而且它們的編程范式也有很大的不同赏参。
帶參數(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ù)式裝飾器的返回值必須是一個函數(shù)把篓,能包裝原有被包裝函數(shù)。也就是說腰涧,Python會在裝飾發(fā)生的時候拿到并且調(diào)用這個返回的函數(shù)結(jié)果韧掩,然后傳遞給被裝飾的函數(shù),這就是為什么我們在裝飾器的實現(xiàn)里嵌套定義了三層的函數(shù)窖铡,最里層的那個函數(shù)是新的替換函數(shù)疗锐。
因為閉包
的特性, wrapped_f()
在不需要像在類式裝飾器
例子中一樣顯示存儲arg1, arg2, arg3
這些值的情況下费彼,就能夠訪問這些參數(shù)滑臊。不過,這恰巧是我覺得“顯式比隱式更好”的例子箍铲。盡管函數(shù)式裝飾器
可能更加精簡一點雇卷,但類式裝飾器
會更加容易理解并因此更容易被修改和維護。