前言
本文介绍下 闭包(closure) , 装饰器(decorator) 合理使用 闭包 可以简化程序复杂度,提高可读性。 闭包常常和 装饰器 一起使用,能很大提高程序的可读性和复用性。
闭包
所谓闭包,指的是:函数定义中存在“嵌套函数”场景,且外部函数返回的是一个函数。返回的函数可以赋给变量,在后续被调用。下面举个计算指数的用例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是exponent_of函数
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
print(square(2)) # 计算2的平方
print(cube(2)) # 计算2的立方
# 输出
4 # 2^2
8 # 2^3
可以看到,上述这种使用闭包的场景:存在多次调用函数的情况(比如要频繁使用square/cube),可以将额外的工作放在外部函数(exponet参数在外部函数定义导入),这样就可以减少多次调用导致不必要的开销,提高程序的运行效率。
装饰器
装饰器 和 闭包 有点类似,区别是:装饰器传入的是函数,装饰器返回的是是内部函数 wrapper()
, wrapper()
函数并不会改变传入的函数,只会添加一些额外的功能行为,最常见的应用场景:检查合法输入(validation_check),缓存装饰器(LRU cache)。 一个基本用例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def my_decorator(func):
def wrapper():
print('wrapper of decorator')
func()
return wrapper
def greet():
print('hello world')
greet = my_decorator(greet)
greet()
# 输出
wrapper of decorator
hello world
Python 中提供了 @
的“语法糖”,上述用例可以修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
def my_decorator(func):
def wrapper():
print('wrapper of decorator')
func()
return wrapper
@my_decorator
def greet():
print('hello world')
greet()
上述用法是最基本的装饰器用法,存在一些问题:
- 该装饰器函数无法导入参数:真实场景下通常会输入不定数量的参数
- 装饰器装饰后,原始函数的元信息发生了变化:
func.__name
变成了wrapper
上述,1.问题可以通过传入 *args, **kwargs
,2.可以通过内置的装饰器 @functools.wrap
进行元信息的保护(通过将原函数的元信息拷贝到对应的装饰器函数中),修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
@my_decorator
def greet(message):
print(message)
greet.__name__
# 输出
'greet'
上述是采用函数作为装饰器使用,也可以采用类作为装饰器,类作为装饰器依赖于内置函数 __call()
函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
# 输出
num of calls is: 1
hello world
example()
# 输出
num of calls is: 2
hello world
很多时候,装饰器也存在“嵌套使用”的情况,这里主要谈下其调用的顺序,对于如下嵌套场景:
1
2
3
4
5
@decorator1
@decorator2
@decorator3
def func():
...
其等价于:
1
decorator1(decorator2(decorator3()))
调用顺序用例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import functools
def my_decorator1(func):
print('enter decorator1')
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator1')
func(*args, **kwargs)
return wrapper
def my_decorator2(func):
print('enter decorator2')
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator2')
func(*args, **kwargs)
return wrapper
@my_decorator1
@my_decorator2
def greet(message):
print(message)
greet('hello world')
# 输出
enter decorator2
enter decorator1
execute decorator1
execute decorator2
hello world