Decorator進(jìn)階指南
在[python裝飾器完全指南基礎(chǔ)篇中],我們已經(jīng)知道了python中的裝飾器本質(zhì)上只是一個(gè)接受一個(gè)函數(shù)對象作為輸入,在該函數(shù)前后增加一些新邏輯,并返回包裝過后的新函數(shù)對象的函數(shù)。
問題一: 有輸入?yún)?shù)的函數(shù)的裝飾器
在基礎(chǔ)篇中,我們所討論的所有被裝飾器裝飾的函數(shù)都是不接收輸入?yún)?shù)的函數(shù)。那么對于有輸入?yún)?shù)的函數(shù),我們應(yīng)該如何定義適用于它們的裝飾器呢?請看如下代碼片段。
def decorator_for_func_with_arguments(func_to_decorate):
def wrapper_that_passes_through_arguments(arg1, arg2):
print('I got args! Look: {} {}'.format(arg1, arg2))
func_to_decorate(arg1, arg2)
return wrapper_that_passes_through_arguments
@ decorator_for_func_with_arguments
def print_full_name(first_name, last_name):
print('I am {} {}'.format(first_name, last_name))
print_full_name('Tony', 'Stark')
# output:
# I got args! Look: Tony Stark
# I am Tony Stark
可以看出,由于裝飾器事實(shí)上返回了一個(gè)新的函數(shù)來代替我們需要裝飾的函數(shù),所以我們只需要確保在裝飾器中返回的新的函數(shù)與原函數(shù)所接受的參數(shù)格式一致即可。
問題二:類方法的裝飾器
Python中的類方法事實(shí)上與函數(shù)一樣,只是固定接收當(dāng)前實(shí)例的引用作為第一個(gè)參數(shù),一般標(biāo)記為self。那么能夠裝飾類方法的裝飾器事實(shí)上也可以用與問題一中一致的方法實(shí)現(xiàn),只不過我們總是要確保返回的函數(shù)所接收的第一個(gè)參數(shù)也是當(dāng)前實(shí)例的引用(self)即可。如下所示。
def decorator_for_instance_method(method_to_decorate):
def wrapper(self, bonus):
# 升職加薪,獎(jiǎng)金增加一倍 d=====( ̄▽ ̄*)b
bonus = bonus * 2
return method_to_decorate(self, bonus)
return wrapper
class Salary(object):
def __init__(self):
self.base = 666
@decorator_for_instance_method
def total_compensation(self, bonus):
print('Congrats! You got a total compensation of {}'.format(self.base * 12 + bonus))
salary_instance = Salary()
salary_instance.total_compensation(2048)
# output: Congrats! You got a total compensation of 12088
類似地,我們也可以用python中的args和*kwargs來實(shí)現(xiàn)一個(gè)能夠裝飾接收任意數(shù)目參數(shù)函數(shù)的裝飾器。如下所示。
def decorator_passing_arbitrary_arguments(function_to_decorate):
def wrapper_with_arbitrary_arguments(*args, **kwargs):
print('Received arguments as following')
print(args)
print(kwargs)
function_to_decorate(*args, **kwargs)
return wrapper_with_arbitrary_arguments
@decorator_passing_arbitrary_arguments
def function_with_no_argument():
print('This function does not have any argument')
function_with_no_argument()
# output:
# Received arguments as following
# ()
# {}
# This function does not have any argument
@decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print('This function has arguments')
function_with_arguments(1,2,3)
# output:
# Received arguments as following
# (1, 2, 3)
# {}
# This function has arguments
@decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, name)
print('{}, {}, {}'.format(a, b, c))
print('{}'.format(name))
function_with_named_arguments(1, 2, 3, name='robot')
# output:
# Received arguments as following
# (1, 2, 3)
# {'name': 'robot'}
# 1, 2, 3
# robot
class Salary(object):
def __init__(self):
self.base = 666
@decorator_passing_arbitrary_arguments
def total_compensation(self, bonus):
print('Congrats! You got a total compensation of {}'.format(self.base * 12 + bonus))
salary = Salary()
salary.total_compensation(2048)
# salary.total_compensation(2048)
# Received arguments as following
# (<__main__.Salary object at 0x1070b5f28>, 2048)
# {}
# Congrats! You got a total compensation of 10040
問題三:給裝飾器傳入?yún)?shù)
上面我們討論了裝飾器所裝飾的函數(shù)參數(shù)有關(guān)的問題。接下來我們討論如何實(shí)現(xiàn)能夠接收參數(shù)的裝飾器。
看過之前文章內(nèi)容的讀者相比已經(jīng)知道,所謂裝飾器其實(shí)就是接收一個(gè)函數(shù)作為輸入,并返回另一個(gè)函數(shù)的函數(shù)。這種情況下,由于裝飾器的函數(shù)簽名已經(jīng)固定,所以我們無法直接傳入除輸入函數(shù)之外的參數(shù)。如下面代碼所示。
# 裝飾器就是接收一個(gè)函數(shù)作為輸入的函數(shù)
def my_decorator(func):
print('This is an ordinary function')
def wrapper():
print('This is the wrapper function that will be returned')
func()
# 只有上述格式簽名的函數(shù)才能作為裝飾器
# 下面代碼等于
# lazy_function = my_decorator(lazy_function)
@my_decorator
def lazy_function():
print('zzzzz')
# output:
# This is an ordinary function
lazy_function()
# output:
# This is the wrapper function that will be returned
# zzzzz
上面代碼說明當(dāng)我們使用@my_decorator的時(shí)候,事實(shí)上就是執(zhí)行了lazy_function = my_decorator(lazy_function)。因此在無法直接改變裝飾器簽名的情況下,我們需要采用一些別的辦法來實(shí)現(xiàn)我們的目標(biāo)——實(shí)現(xiàn)一個(gè)能夠返回裝飾器的函數(shù)來替裝飾器接收參數(shù),并使用閉包(對閉包概念不熟悉的讀者,請參閱這篇文章
)的方法來將這些參數(shù)傳遞到裝飾器中。換句話說,我們需要一個(gè)裝飾器工廠函數(shù)來替我們動態(tài)生成裝飾器。
首先我們實(shí)現(xiàn)一個(gè)裝飾器工廠函數(shù),如下面的代碼所示。
def decorator_maker():
print('This is a factory generating decorators on the fly. This function is called once we want a fresh decorator.')
def my_decorator(func):
print('This is the decorator generated on the fly. This function is called when the decoration happens.')
# 類似地,我們還是在裝飾器中定義一個(gè)wrapper還包裝原函數(shù)
def wrapper():
print('This is the wrapper around the decorated function. This function is called once the decorated function is called.')
return func()
return wrapper
print('The decorator function created on the fly is returned.')
return my_decorator
def func():
print('This is the function decorated.')
fresh_decorator = decorator_maker()
# output:
# This is a factory generating decorators on the fly. This function is called once we want a fresh decorator.
# The decorator function created on the fly is returned.
func = fresh_decorator(func)
# output:
# This is the decorator generated on the fly. This function is called when the decoration happens.
func()
# output:
# This is the wrapper around the decorated function. This function is called once the decorated function is called.
# This is the function decorated.
基于如上代碼,我們可以用更加pythonic的方式去使用這個(gè)裝飾器工廠函數(shù)。如下所示。
@decorator_maker()
def func():
print('This is the function decorated.')
# output:
# This is a factory generating decorators on the fly. This function is called once we want a fresh decorator.
# The decorator function created on the fly is returned.
# This is the decorator generated on the fly. This function is called when the decoration happens.
func()
# output:
# This is the wrapper around the decorated function. This function is called once the decorated function is called.
# This is the function decorated.
上面的例子說明,我們是事實(shí)上可以用一個(gè)裝飾器工廠返回的函數(shù)作為@語法中的裝飾器使用。既然如此,我們自然也就可以給這個(gè)工廠函數(shù)傳入?yún)?shù)。如下面代碼所示。
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
print('This is the decorator factory. Input arguments are: {}, {}.'.format(decorator_arg1, decorator_arg2))
def my_decorator(func):
# 利用閉包特性來將工廠函數(shù)接收的參數(shù)傳遞給動態(tài)生成的裝飾器
print('This is the decorator function created on the fly.')
print('Arguments are passed in from outer function using closure: {}, {}.'.format(decorator_arg1, decorator_arg2))
# 注意這里wrapper函數(shù)接收的參數(shù)是被該裝飾器裝飾的函數(shù)
# 不要和上面工廠函數(shù)接收的參數(shù)混淆
def wrapper(function_arg1, function_arg2):
print('This is the wrapper around the decorated function.')
print('This function can access all the variables.')
print('Variables from the decorator factory: {}, {}.'.format(decorator_arg1, decorator_arg2))
print('Variables from the decorated function: {}, {}.'.format(function_arg1, function_arg2))
return func(function_arg1, function_arg2)
return wrapper
return my_decorator
@decorator_maker_with_arguments('Avengers', 'Justice League')
def func(function_arg1, function_arg2):
print('This is the function decorated.')
print('It accepts two arguments: {}, {}.'.format(function_arg1, function_arg2))
# output:
# This is the decorator factory. Input arguments are: Avengers, Justice League.
# This is the decorator function created on the fly.
# Arguments are passed in from outer function using closure: Avengers, Justice League.
func('Captain America', 'Bat Man')
# output:
# This is the wrapper around the decorated function.
# This function can access all the variables.
# Variables from the decorator factory: Avengers, Justice League.
# Variables from the decorated function: Captain America, Bat Man.
# This is the function decorated.
# It accepts two arguments: Captain America, Bat Man.
上面例子中,我們將字符串常量作為參數(shù)傳遞給了裝飾器工廠函數(shù)。與平常的python函數(shù)一樣,這一函數(shù)也可以接收變量作為參數(shù)。如下面所示。
a1 = 'Avenagers'
a2 = Justice League'
b1 = 'Captain America'
b2 = 'Bat Man'
@decorator_maker_with_arguments(a1, a2)
def func(function_arg1, function_arg2):
print('This is the function decorated.')
print('It accepts two arguments: {}, {}.'.format(function_arg1, function_arg2))
# output:
# This is the decorator factory. Input arguments are: Avengers, Justice League.
# This is the decorator function created on the fly.
# Arguments are passed in from outer function using closure: Avengers, Justice League.
func(b1, b2)
# output:
# This is the wrapper around the decorated function.
# This function can access all the variables.
# Variables from the decorator factory: Avengers, Justice League.
# Variables from the decorated function: Captain America, Bat Man.
# This is the function decorated.
# It accepts two arguments: Captain America, Bat Man.
通過上述討論,可以看出對于裝飾器工廠函數(shù)而言,調(diào)用它與調(diào)用普通函數(shù)完全相同。我們甚至也可以使用*args及**kwargs來接受可變長度的參數(shù)。但有一點(diǎn)需要注意,那就是裝飾器裝飾函數(shù)的過程只發(fā)生一次,那就是python編譯器import當(dāng)前代碼文件的時(shí)候。因此一旦文件已經(jīng)被import過,那我們就無法修改裝飾器工廠接收的參數(shù),也無法修改已經(jīng)生成的裝飾器裝飾過的函數(shù)了。
Reference
文中部分內(nèi)容翻譯自如下文章。翻譯部分版權(quán)歸原作者所有。
https://gist.github.com/Zearin/2f40b7b9cfc51132851a