12.1. 前置知识
前置知识 1: 局部作用域的生命周期。
- 每次调用函数时,都会创建一个新的局部作用域。
- 函数执行完毕后,该作用域就会被销毁,其中的局部变量,也会随之释放。
def outer():
num = 10
num += 1
print(num)
outer() #11
outer() #11
outer() #11
在上述代码中:每次调用 outer(),num 都会重新生成,不会保存上一次的值。
前置知识 2:内层函数访问外层变量。
- 【内层函数】可以访问到【外层函数】作用域中的变量。
- 访问外层变量不用nonlocal,修改外层变量时要使用nonlocal。
def outer():
num = 10
def inner():
print(num)
inner()
outer()
如下代码验证了:理论上num变量应该在outer函数调用完销毁,但实际并没有。
def outer():
num = 10
def inner():
print(num)
return inner
f = outer()
f()
12.2. 什么是闭包
- 闭包 = 内层函数 + 被内层函数引用的外层变量。
- 产生闭包的三个条件如下:
- 必须有函数嵌套
- 内层函数使用了外层函数的变量
- 外层函数返回内层函数
12.3. 闭包的基本形式
def outer():
num = 10
def inner():
nonlocal num
num += 1
print(num)
return inner
f = outer()
f() # 11
f() # 12
f() # 13
12.4. 闭包是如何保存外层变量的?
外层变量会被保存到 闭包单元(cell)中,例如下面代码中,那些被 inner函数所使用到的outer函数中的局部变量,会被封存在闭包单元(cell) 中,这些cell组成一个 __closure__ 元组,保存在了inner函数上。
def outer():
num = 10
print(id(num)) # 140707460170952
def inner():
nonlocal num
num += 1
print(num)
return inner
f = outer()
print(f.__closure__) # __closure__的值是一个元组,元组中保存着被inner函数所“挽救”下来的数据
print(f.__closure__[0].cell_contents) # 10
print(id(f.__closure__[0].cell_contents)) # 140707460170952
思考:内层函数inner会保存外层函数outer中所有的数据吗?
不会,只会保存inner中所用到的。
def outer():
num = 10
msg = '你好啊!'
print(msg)
def inner():
nonlocal num
num += 1
print(num)
return inner
f = outer()
print(f.__closure__) # 发现__closure__中只有num,没有msg
12.5. 注意点
1. 每次获得一个新闭包,互不影响(闭包之间是互相独立的)。
def outer():
num = 10
def inner():
nonlocal num
num += 1
print(num)
return inner
f1 = outer()
f1()
f1()
f1()
print('*****************')
f2 = outer()
f2()
2. 外层变量为可变对象时仍互不影响。
def outer():
nums = []
def inner(value):
nums.append(value)
print(nums)
return inner
# 每次调用 outer() 都创建一个新的 nums
f1 = outer()
f1(10)
f1(20)
f1(30)
print('**********************')
f2 = outer()
f2(666)
12.6. 闭包的优点
- 可以“记住”状态:不用全局变量,也不用写类,就能在多次调用之间保存数据。
- 可以做“配置过的函数”:先传一部分参数,把环境固定住,得到一个定制版函数。
- 可以实现简单的“数据隐藏”:外层变量对外不可见,只能通过内层函数访问。
- 是装饰器(decorator)等高级用法的基础。
小案例:使用闭包实现对文字的美化效果。
def beauty(char, n):
def show_msg(msg):
print(char * n + msg + char * n)
return show_msg
show1 = beauty('*', 3)
show1('你好啊')
show1('大赛人')
show2 = beauty('@', 5)
show2('你好啊')
show2('大赛人')
12.7. 闭包的缺点
1.理解成本较高:对初学者不太友好,滥用会让代码难读。
2.如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
3.很多场景下,其实用【类 + 实例属性】会更清晰,闭包不一定是最优解。
例如将上述的文字美化效果,用类实现:
class Beauty:
def __init__(self, char, n):
self.char = char
self.n = n
def show_msg(self, msg):
print(self.char * self.n + msg + self.char * self.n)
b1 = Beauty('*', 3)
b1.show_msg('你好啊')
b1.show_msg('大赛人')
b2 = Beauty('#', 5)
b2.show_msg('你好啊')
b2.show_msg('大赛人')
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END












请登录后查看评论内容