当前位置:首页>python>Python之闭包

Python之闭包

  • 2026-08-22 09:42:57
Python之闭包

第六章 Python之闭包

1.前置知识

前置知识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()

2. 什么是闭包

闭包 = 内层函数 + 被内层函数引用的外层变量。

产生闭包的三个条件如下:

  1. 必须有函数嵌套。
  2. 内存函数使用了外层函数的变量。
  3. 外层函数返回内存函数。
# 如下代码验证了:理论上num变量应该在outer函数调用完销毁,但实际上没有。def outer():    num = 10    def inner():        nonlocal num        num += 1        print(num)    return innerf = outer()f() # 11f() # 12f() # 13

3. 闭包的基本形式

在上述代码的基础上,我们在inner函数中不仅要打印num,还要去修改num(修改时记得加nonlocal num),最终发现:num的值居然还可以一直增加。所以截至目前,证明了一件事:本应该随着outer函数调用结束而死去的num并没有死去,并且inner函数依然可以对num进行读取和修改。所以我们观察如上的代码形式,完全符合上述的闭包产生条件,所以此时就出现了闭包。

4.闭包如何保存外层变量?

外层变量会被保存到闭包单元(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

5.注意点

  • 每次获得一个新闭包,都是互不影响的(闭包直接是互相独立的)。
# 注意点:# 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]

6.闭包的优缺点

优点:

  1. 可以记住状态:不用全局变量,也不用写类,就能在多次调用直接保存数据。
  2. 可以做配置过的函数:先传一部分参数,把环境固定住,得到一个定制版函数。
  3. 可以实现简单的数据隐藏,外层变量对外不可见,只能通过内存函数访问。
  4. 是装饰器(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#####

缺点:

  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('Hello') # ***Hello***b1.show_msg('Python') # ***Python***print('**********')b2 = Beauty('#', 5)b2.show_msg('Hello') # #####Hello#####b2.show_msg('Python') # #####Python#####

最新文章

随机文章