在编程时,我们会处理到很多数据。但是对于一些数据,我们只想使用,不想修改。我们可以使用 闭包
来防止不经意间的数据修改。
闭包的作用主要有两个:
- 保护数据安全
- 保护数据干净性
满足下面两个条件的函数就是一个实用的闭包:
- 在嵌套函数内,使用非全局变量(且不使用本层变量)
- 将嵌套函数本身返回
我们可以用.__closure__
方法来查看一个函数是否是一个闭包。当返回值为None时,说明该函数不是闭包,当返回值中有变量的内存地址时,说明该函数是一个闭包。
例如,下面的函数就是一个闭包:
1 2 3 4 5 6 7
| def func(): a = 10 def foo(): print(a) return foo f = func() print(f.__closure__)
|
输出的结果为:
1
| (<cell at 0x000001F7D6277648: int object at 0x00000000669D8190>,)
|
方法中的 closure
就是 闭包
的意思。返回的 cell
是一个 单元格
对象,用来储存闭包中需要使用到的变量。
当函数执行完成后(也就是 fun()
这个步骤),函数体会被销毁。但是闭包 foo
被返回给了 f
,还会被用到,所以照常存在。而闭包中需要使用的变量 a
,此时已经不在函数 func
中,因为 func
本身已经不复存在。变量 a
在函数销毁后,升级为 自由变量
。
闭包是怎么做到保护数据的作用的呢?
比如我们现在有这样一个需求:使用一个函数,每天输入当天营业额,打印出这几天的平均营业额。
如果我们把营业额的数据储存在列表中,放到全局,可以这样实现功能:
1 2 3 4 5 6 7 8 9 10 11 12
| lst = [] def ave_turnover(turnover_today): lst.append(turnover_today) ave = sum(lst) / len(lst) print(ave) ave_turnover(12000) ave_turnover(15000) ave_turnover(14000) ave_turnover(12000) ave_turnover(25000) ave_turnover(21000) ave_turnover(9000)
|
输出的结果为:
1 2 3 4 5 6 7
| 12000.0 13500.0 13666.666666666666 13250.0 15600.0 16500.0 15428.57142857143
|
如果有一天,我们不小心在全局对 lst
列表做了修改:
1 2 3 4 5 6 7 8 9 10 11 12 13
| lst = [] def ave_turnover(turnover_today): lst.append(turnover_today) ave = sum(lst) / len(lst) print(ave) ave_turnover(12000) ave_turnover(15000) lst[1] = 10 ave_turnover(14000) ave_turnover(12000) ave_turnover(25000) ave_turnover(21000) ave_turnover(9000)
|
输出的结果为:
1 2 3 4 5 6 7
| 12000.0 13500.0 8670.0 9502.5 12602.0 14001.666666666666 13287.142857142857
|
这就导致了数据的改变,使得第三天之后的数据全都不准确。
如果我们把重要的数据 lst
封装到闭包中保护起来,就不会有这种烦恼了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def ave_turnover(): lst = [] def inner(turnover_today): lst.append(turnover_today) ave = sum(lst) / len(lst) print(ave) return inner f = ave_turnover() f(12000) f(15000) lst = [10] f(14000) f(12000) f(25000) f(21000) f(9000)
|
输出的结果为:
1 2 3 4 5 6 7
| 12000.0 13500.0 13666.666666666666 13250.0 15600.0 16500.0 15428.57142857143
|
对于闭包,除了 .__closure__
方法之外,还有可以用来查看自由变量的 .__code__.co_freevars
方法,和可以用来查看局部变量的 .__code__.co_varnames
方法:
1 2 3 4 5 6 7 8 9 10 11
| def ave_turnover(): lst = [] def inner(turnover_today): lst.append(turnover_today) ave = sum(lst) / len(lst) print(ave) return inner f = ave_turnover() print(f.__closure__) print(f.__code__.co_freevars) print(f.__code__.co_varnames)
|
输出的结果为:
1 2 3
| (<cell at 0x0000027A31A27648: list object at 0x0000027A31AD8C08>,) ('lst',) ('turnover_today', 'ave')
|
没有将嵌套的函数返回的函数也可以是一个闭包,但是这个闭包没有办法被使用:
1 2 3 4 5 6
| def func(): a = 10 def foo(): print(a) print(foo.__closure__) func()
|
输出的结果为:
1
| (<cell at 0x000001BE85EA7648: int object at 0x00000000669D8190>,)
|
闭包也可以通过外层函数加两个括号的方法来调用:
1 2 3 4 5 6
| def func(): a = 10 def foo(): print(a) return foo func()()
|
外层函数的参数也是局部变量,所以内层函数使用外层参数的形式也可以构成闭包:
1 2 3 4 5 6 7 8 9 10 11 12
| def wrapper(a, b): def inner(): print(a) print(b) return inner a = 11 b = 12 ret = wrapper(a, b) print(ret.__closure__) print(ret.__code__.co_freevars) print(ret.__code__.co_varnames) ret()
|
返回的结果为:
1 2 3 4 5
| (<cell at 0x0000021A67AFB648: int object at 0x00000000702D81B0>, <cell at 0x0000021A67AFB9A8: int object at 0x00000000702D81D0>) ('a', 'b') () 11 12
|
闭包的应用场景主要有两个:
- 装饰器
- 防止数据被误改动