前置知识1:局部作用域的生命周期。
每次调用函数时,都会创建一个新的局部作用域。 函数执行完毕后,该作用域就会被销毁,其中的局部作用域,也会随之释放。
# 前置知识一:# 1.每次调用函数时,Python 都会为函数创建一个新的局部作用域# 2.函数执行完毕后,这个局部作用域会被销毁,其中的局部变量也会随之被释放def outer(): num = 10 num += 1 print(num)# 调用3次函数,结果都是11,验证局部作用域执行完会被销毁,如果没有销毁,num会累加# 在上述代码中:每次调用 outer(),num 都会重新生成,不会保存上一次的值。 outer() # 11outer() # 11outer() # 11
前置知识2:内层函数访问外层变量。
内层函数可以访问到外层函数作用域中的变量。访问外层变量不用 nonlocal,修改外层变量时要使用nonlocal。
# 前置知识二:# 1.在 Python 中,【内层函数】可以访问其【外层函数】作用域中的变量# 2.访问外层函数变量无需使用 nonlocal;但修改外层变量时要使用 nonlocaldef outer(): num = 10 def inner(): nonlocal num num = 90 print(1, num) # 1 90 inner() print(2, num) # 2 90outer()
闭包 = 内层函数 + 被内层函数引用的外层变量。产生闭包的三个条件如下:
必须有函数嵌套。 内存函数使用了外层函数的变量。 外层函数返回内存函数。
# 如下代码验证了:理论上num变量应该在outer函数调用完销毁,但实际上没有。def outer(): num = 10 def inner(): nonlocal num num += 1 print(num) return innerf = outer()f() # 11f() # 12f() # 13在
上述代码的基础上,我们在inner函数中不仅要打印num,还要去修改num(修改时记得加nonlocal num),最终发现:num的值居然还可以一直增加。所以截至目前,证明了一件事:本应该随着outer函数调用结束而死去的num并没有死去,并且inner函数依然可以对num进行读取和修改。所以我们观察如上的代码形式,完全符合上述的闭包产生条件,所以此时就出现了闭包。
外层变量会被保存到
闭包单元(cell)中,例如下面代码中,那些被inner函数所使用到的outer函数中的局部变量,会被封存在闭包单元(cell)中,这些cell组成了一个__closure__元组,保存在了inner函数中。
# 什么是闭包? ———— 闭包 = 内层函数 + 被内层函数所引用的外层变量# 闭包产生的条件:# 1.要有函数嵌套。# 2.在【内层函数】中,要访问【外层函数】的变量。# 3.并且【外层函数】要返回【内层函数】。———— 只有返回了内层函数,闭包才能“活下来”def outer(): num = 10 def inner(): nonlocal num num += 1 print(num) return innerf = outer()f() # 11f() # 12f() # 13# 结论:# 1.outer函数中,被inner所使用到的那些变量,会被封存到【闭包单元(cell)】中。# 2.这些 cell 会组成一个 __closure__ 元组,最终放在了 inner 函数身上。# 打印 __closure__ 元组print(f.__closure__) # (<cell at 0x0000022C1A6A2CB0: int object at 0x00007FF86E93FB38>,)# 打印 __closure__ 元组中的某一项print(f.__closure__[0]) # <cell at 0x0000022C1A6A2CB0: int object at 0x00007FF86E93FB38># 打印 __closure__ 元组中的某一项的具体值print(f.__closure__[0].cell_contents) # 13思考:内层函数inner会保存外层函数outer中所有的数据吗?
不会,只会保存inner所用到的数据。
def outer(): num = 10 msg = 'Hello World!' print(msg) def inner(): nonlocal num num += 1 print(num) return innerf = outer()# 发现__closure__中只有num,并没有msg。print(f.__closure__) # (<cell at 0x000001D473CC2CE0: int object at 0x00007FF86E93FAD8>,)print(f.__closure__[0]) # <cell at 0x000001D473CC2CE0: int object at 0x00007FF86E93FAD8>print(f.__closure__[0].cell_contents) # 10# 注意点:# 1. 调用n次外层函数,就会得到n个不同的闭包,并且这些闭包之间互不影响def outer(): num = 10 def inner(): nonlocal num num += 1 print(num) return innerf1 = outer()f1() # 11f1() # 12f1() # 13print('**********')f2 = outer()f2() # 11f2() # 12f2() # 13# 2. 内层函数中用到的外层变量是可变对象,多个闭包之间依然互不影def outer(): num = [] def inner(value): num.append(value) print(num) return innerf1 = outer()f1(10) # [10]f1(20) # [10, 20]f1(30) # [10, 20, 30]print('**********')f2 = outer()f2(666) # [666]
优点:
可以 记住状态:不用全局变量,也不用写类,就能在多次调用直接保存数据。可以做 配置过的函数:先传一部分参数,把环境固定住,得到一个定制版函数。可以实现简单的 数据隐藏,外层变量对外不可见,只能通过内存函数访问。是 装饰器(decorator)等高级用法的基础。
# 文字美化效果def beauty(char, n): def show_msg(msg): print(char * n + msg + char * n) return show_msgshow1 = beauty('*', 3)show1('Hello') # ***Hello***show1('Python') # ***Python***print('**********')show2 = beauty('#', 5)show2('Hello') # #####Hello#####show2('Python') # #####Python#####
缺点:
理解成本较高:对初学者不太友好,滥用会让代码难读。 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。 很多场景下,其实用 类 + 实例属性会更清晰,闭包不一定是最优解。
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('Hello') # ***Hello***b1.show_msg('Python') # ***Python***print('**********')b2 = Beauty('#', 5)b2.show_msg('Hello') # #####Hello#####b2.show_msg('Python') # #####Python#####