作者:无尘粉笔
链接:https://www.zhihu.com/question/271201015/answer/2387427580
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
装饰器(decorator)是Python一个进阶的用法,通常以如下形式出现,但是它到底怎么用呢?且看本文细细分解。
@decorator # 装饰器
def self_defined_function():
print("Self-defined function is called.")
1. 什么时候需要用装饰器?
试想你有一系列函数,例如如下所示的两个函数(分别计算和与积):
def my_sum_function(*args):
print("The sum is", sum(args))
def my_product_function(*args):
res = 1
for x in args:
res *= x
print("The product is", res)
my_sum_function(1, 2, 3, 4)
my_product_function(1, 2, 3, 4, 5)
现在,你需要为这一系列函数添加一个一样的功能,就是统计输入参数的个数,同时检查里面是否有0,那么低阶的解决方法就是为每一个函数单独添加部分代码,即
def my_sum_function(*args):
print("No. of input args is", len(args)) # 重复部分
contain_zero = any([x == 0 for x in args]) # 重复部分
print("Input arguments contain 0:", contain_zero) # 重复部分
print("The sum is", sum(args))
def my_product_function(*args):
print("No. of input args is", len(args)) # 重复部分
contain_zero = any([x == 0 for x in args]) # 重复部分
print("Input arguments contain 0:", contain_zero) # 重复部分
res = 1
for x in args:
res *= x
print("The product is", res)
my_sum_function(1, 2, 3, 4)
my_product_function(0, 1, 2, 3, 4, 5)
这样写的话,代码中存在大量重复的部分,有没有好一点的办法呢?也许你已经想到了,就是把添加的这部分功能封装成一个单独的函数,然后让每一个函数单独调用这个函数(嵌套调用),这样就能减少写代码过程中的“Ctrl+C和Ctrl+V”,即
def my_sum_function(*args):
additional_function(*args) # 嵌套调用
print("The sum is", sum(args))
def my_product_function(*args):
additional_function(*args) # 嵌套调用
res = 1
for x in args:
res *= x
print("The product is", res)
def additional_function(*args):
print("No. of input args is", len(args))
contain_zero = any([x == 0 for x in args])
print("Input arguments contain 0:", contain_zero)
my_sum_function(1, 2, 3, 4)
my_product_function(0, 1, 2, 3, 4, 5)
那有没有更pythonic的写法呢?这里就可以用到装饰器了,实现方法如下
def a_decorator(f):
def additional_function(*args):
print("No. of input args is", len(args))
contain_zero = any([x == 0 for x in args])
print("Input arguments contain 0:", contain_zero)
f(*args)
return additional_function
@a_decorator
def my_sum_function(*args):
print("The sum is", sum(args))
@a_decorator
def my_product_function(*args):
res = 1
for x in args:
res *= x
print("The product is", res)
my_sum_function(1, 2, 3, 4)
my_product_function(0, 1, 2, 3, 4, 5)
2. 装饰器的执行机制
现在我么已经知道了装饰器的使用场景:就是为函数定制化额外功能的时候,可是添加装饰器。使用装饰器可以使代码更加简洁。那么装饰器是如何工作的呢?我们以如下例子说明:
def a_decorator(f): # 函数作为参数被传入
print(f"Function {f.__name__} is passed as the augument!")
def additional_function(*args): # 函数嵌套
print("No. of input args is", len(args))
contain_zero = any([x == 0 for x in args])
print("Input arguments contain 0:", contain_zero)
f(*args) # 最终执行目标函数my_sum_function的地方
return additional_function # 函数作为返回对象
@a_decorator
def my_sum_function(*args):
print("The sum is", sum(args))
my_sum_function(1, 2, 3, 4)
运行结果如下
$ python demo.py
Function my_sum_function is passed as the augument!
No. of input args is 4
Input arguments contain 0: False
The sum is 10
在进一步解释运行机制之前,我们需要明确几个问题:
- 一、在Python中,一切皆对象,包括函数。一个函数可以作为另一个函数的参数,一个函数也可以作为另一个函数的返回对象;
- 二、如果在一个函数体中定义了另一个函数,称为函数嵌套,前者称为enclosing function,后者称为enclosed function或者nested function
根据运行结果,我们可以反推出装饰器的工作机制:
- my_sum_function作为参数传入a_decorator函数,并开始执行,因此首先打印出“Function my_sum_function is passed as the augument!”
- 随后a_decorator函数执行过程中返回了additional_function函数对象,然后开始执行additional_function(1, 2, 3, 4),于是打印出“No. of input args is 4”和“Input arguments contain 0: False”
- 在additional_function函数嵌套调用了my_sum_function函数,因此最后打印“The sum is 10”
也就是说,上述例子如果不用@符号,和下面是完全等价的
def a_decorator(f):
print(f"Function {f.__name__} is passed as the augument!")
def additional_function(*args):
print("No. of input args is", len(args))
contain_zero = any([x == 0 for x in args])
print("Input arguments contain 0:", contain_zero)
f(*args)
return additional_function
def my_sum_function(*args):
print("The sum is", sum(args))
a_decorator(my_sum_function)(1, 2, 3, 4) # 注意理解这一行
3. 闭包
在上述例子中,对于a_decorator函数的设计使用了闭包(closure)的概念[1],即
- 一个函数中嵌套了另一个函数,如上述a_decorator函数中嵌套了additional_function函数
- enclosed function中直接使用了enclosing funcion中的参数,如上述additional_function函数中使用了a_decorator函数的参数f
- 最终返回enclosing funcion,如上述例子中最终返回了additional_function
满足以上三个条件的称为Python闭包。