函数装饰器和闭包
1装饰器前提:
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。
nonlocal 是新近出现的保留关键字,在 Python 3.0 中引入。作为 Python 程序员,如果严格遵守基于类的面向对象编程方式,即便不知道这个关键字也不会受到影响。然而,如果你
想自己实现函数装饰器,那就必须了解闭包的方方面面,因此也就需要知道 nonlocal 。
1
自定义一个装饰器
2
3
>>> def deco(func):
4
... def inner():
5
... print('running inner()')
6
... return inner
7
...
8
>>> @deco
9
... def target():
10
... print('running target()')
11
...
12
>>> target()
13
running inner()
14
>>> target
15
<function deco.<locals>.inner at 0x7fd604acd1e0>
16
17
1
"""
2
练习装饰器
3
2019年11月16日
4
作者:戴昊龙
5
"""
6
regis = []
7
8
9
def registry(func):
10
print("running registry %s" % func)
11
regis.append(func)
12
return func
13
14
15
16
def func1():
17
print("running func1")
18
19
20
21
def func2():
22
print("running func2")
23
24
25
def func3():
26
print("running func3")
27
28
29
def main():
30
print("running main")
31
print("regis---->", regis)
32
func1()
33
func2()
34
func3()
35
36
37
if __name__ == '__main__':
38
main()
39
1
running registry <function func1 at 0x0000000002207A60>
2
running registry <function func2 at 0x0000000002207EA0>
3
running main
4
regis----> [<function func1 at 0x0000000002207A60>, <function func2 at 0x0000000002207EA0>]
5
running func1
6
running func2
7
running func3
8
2、变量的作用域
1
>>> b = 6
2
>>> def test():
3
... a = 9
4
... print(a)
5
... print(b)
6
... b = 10
7
...
8
>>> test()
9
9
10
Traceback (most recent call last):
11
File "<stdin>", line 1, in <module>
12
File "<stdin>", line 4, in test
13
UnboundLocalError: local variable 'b' referenced before assignment
14
上面这个例子看起来很奇怪,但是实际上,Python 编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了。生成的字节码证实了这种判断,Python 会尝试从本地环境获取 b 。后面调用 test 时,test 的定义体会获取并打印局部变量 a 的值,但是尝试获取局部变量 b 的值时,发现 b 没有绑定值。
1
>>> def f():
2
... a = 9
3
... print(a)
4
... global b
5
... print(b)
6
... b =10
7
...
8
>>> f()
9
9
10
6
11
>>> b
12
10
如果在函数中赋值时想让解释器把 b 当成全局变量,要使用 global 声明:
3、闭包
怎么理解闭包?有难度的一个概念:最肤浅的理解,就是函数里面定义函数
在函数内部定义函数不常见,直到开始使用匿名函数才会这样做。而且,只有涉及嵌套函数时才有闭包问题。
其实,闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的
非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
1
# 比方说一个avg函数,用来实现移动求平均
2
# 每次都叠加求平均
3
avg(10) ---- 10.0
4
avg(11)----- 10.5
5
avg(12)------11.0
6
7
# 怎么来实现呢? 普通的应该就是用类和对象来实现,第二种方法可以用高阶函数来实现
8
# example(1),用类来实现
9
class Average():
10
def __init__(self):
11
self.series = []
12
13
def __call__(self, new_value):
14
self.series.append(new_value)
15
total = sum(self.series)
16
return total/len(self.series)
17
18
a1 = Average()
19
print(a1(10))
20
21
# example(2), 用高阶函数实现
22
def make_averager(): # example2
23
series = [] # 这个就是闭包
24
25
def average(num):
26
series.append(num) # series是自由变量
27
total = sum(series)
28
return total/len(series)
29
return average # 每次返回的是一个函数对象
30
31
32
a1 = make_averager()
33
print(a1(10))
34
print(a1(20))
35
36
# example(3), 例2中,其实函数的效率不是很高,因为每次都在重复的求取sum,len, 这里我们可以改进一下
37
def make_average():
38
count = 0
39
total = 0
40
41
def average(num):
42
nonlocal count, total # 没有这句声明那这个example3就是错的,为什么呢?
43
"""
44
数字、字符串、元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑
45
定,例如 count = count + 1 ,其实会隐式创建局部变量 count 。这样, count 就不是自由 怎么理解这段话?
46
变量了,因此不会保存在闭包中。
47
当i = 10的时候。 实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为 11 的int对象,i引用自这个新的对象。
48
49
"""
50
count += 1
51
total = total + num
52
return total/count
53
return average
54
"""
55
nonlocal 它的作用是把变量标记为自由变量,
56
即使在函数中为变量赋予新值了,也会变成自由变量。如果为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。
57
"""
闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。
4、 正式开始装饰器
前面的都是准备工作, 这个部分先不学习,先搞懂装饰器的基础知识