python-理解闭包

了解闭包之前,有必要先弄清楚 python 的函数作用域

变量作用域

  • 局部:local
  • 全局:global
  • 非全局:nonlocal

Example-1

无声明的情况下,赋值即私有,若外部有相同变量名则将其遮挡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = 1
print(a)

def func_enclosed():
a = 2
print(a)

def func_local():
a = 3
print(a)

func_local()
print(a)

func_enclosed()
print(a)
1
2
3
2
1

Example-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 1
print(a)

def func_enclosed():
print(a)

def func_local():
print(a)

func_local()
print(a)

func_enclosed()
print(a)
1
1
1
1
1

Example-3

内层函数可以通过声明的方式直接修改外部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = 1
print(a)

def func_enclosed():
global a
a = 2
print(a)

def func_local():
a = 3
print(a)

func_local()
print(a)

func_enclosed()
print(a)
1
2
3
2
2

Example-4

位于最内层的函数,通过 global 声明,会越过中间层,直接修改全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = 1
print(a)

def func_enclosed():
a = 2
print(a)

def func_local():
global a
a = 3
print(a)

func_local()
print(a)

func_enclosed()
print(a)
1
2
3
2
3

Example-5

global 声明其实是一种绑定关系,意思是告诉解释器,不用创建新变量了,我用的是外面那个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = 1
print(a)

def func_enclosed():
global a
a = 2
print(a)

def func_local():
global a
a = 3
print(a)

func_local()
print(a)

func_enclosed()
print(a)
1
2
3
3
3

Example-6

位于最内层的函数,如果仅想要修改中间层变量,而不是全局变量,可使用 nonlocal 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = 1
print(a)

def func_enclosed():
a = 2
print(a)

def func_local():
nonlocal a
a = 3
print(a)

func_local()
print(a)

func_enclosed()
print(a)
1
2
3
3
1

Example-7

nonlocal 只能绑定在中间层定义的变量,如果中间层变量被声明为全局变量,则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = 1
print(a)

def func_enclosed():
global a
a = 2
print(a)

def func_local():
nonlocal a
a = 3
print(a)

func_local()
print(a)

func_enclosed()
print(a)
File "<ipython-input-8-b4968ab8f1e1>", line 10
    nonlocal a
    ^
SyntaxError: no binding for nonlocal 'a' found

闭包

定义: 延伸了作用域的函数(能访问定义体之外定义的非全局变量), 闭包是一种函数,它会保留定义函数时存在的外层非全局变量的绑定。

avg 函数如下所示:

1
2
3
4
5
6
7
8
>>> avg(10)
10.0

>>> avg(11)
10.5

>>> avg(12)
11

用类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Averager():

def __init__(self):
self.series = []

def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)


avg = Averager()
print(avg(10))
print(avg(11))
print(avg(12))
10.0
10.5
11.0

用闭包实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def make_averager():
series = []

def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)

return averager


avg = make_averager()
print(avg(10))
print(avg(11))
print(avg(12))
10.0
10.5
11.0

优化闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def make_averager():
count = 0
total = 0

def averager(new_value):
# 不声明,会报错,前面 series = [] 为可变对象,count 为不可变对象,内层函数想要修改,必须声明
nonlocal count, total
count += 1
total += new_value
return total / count

return averager


avg = make_averager()
print(avg(10))
print(avg(11))
print(avg(12))
10.0
10.5
11.0

闭包有什么用呢?

  1. 共享变量的时候避免使用了不安全的全局变量
  2. 允许将函数与某些数据关联起来,类似于简化版面向对象编程
  3. 相同代码每次生成的闭包,其延伸的作用域都彼此独立(计数器,注册表)
  4. 函数的一部分行为在编写时无法预知,需要动态实现,同时又想保持接口一致性
  5. 较低的内存开销:类的生命周期远大于闭包
  6. 实现装饰器