page contents

Python 中的闭包是怎么实现的?解释 nonlocal 关键字的作用

说到 Python 闭包,很多人脑子里第一反应就是“函数里面套函数,还能记住外面变量的那个东西”。是的,闭包本质上就是函数+环境的组合。更通俗点说,就是一个函数,不仅能干自己的活,还随身带着一小块记忆卡,把它出生时周围的变量全都揣着,走哪都能用。

attachments-2025-09-Fm201yNQ68bb8eec4da8a.png说到 Python 闭包,很多人脑子里第一反应就是“函数里面套函数,还能记住外面变量的那个东西”。是的,闭包本质上就是函数+环境的组合。更通俗点说,就是一个函数,不仅能干自己的活,还随身带着一小块记忆卡,把它出生时周围的变量全都揣着,走哪都能用。

想象一下,你在公司写了个工具函数,里面用到了一个局部变量,正常来说函数执行完,这个局部变量就“说拜拜”了。但是闭包不同,它会让这个局部变量一直活在内存里,供你下次调用的时候继续使用,这就是闭包的神奇之处。

闭包是怎么实现的呢?从 Python 的底层机制看,其实就是编译函数的时候,把外部作用域的变量“捕获”下来,存放到一个叫 __closure__ 的地方。这个 __closure__ 是一个元组,里面每个元素都是一个 cell 对象,存的就是被捕获的变量值。只要闭包函数还在引用这些 cell,外部作用域的变量就不会被垃圾回收清掉。

举个最简单的例子:

defouter():

    x = 10

    definner():

        print(x)

    return inner


f = outer()

f()  # 输出 10

这里 outer() 执行完后,按理 x 变量应该没了,但因为 inner 还在用它,所以 x 被保存在 f.__closure__ 里,f() 再次调用时就能正常访问。那 nonlocal 又是啥用? 很多人初学闭包时都会遇到一个坑:闭包可以读取外部变量,但不能直接修改,因为 Python 会把赋值语句当成创建一个新的局部变量,跟外面的同名变量没有关系。比如:

defouter():

    x = 10

    definner():

        x = x + 1  # 这里会报错 UnboundLocalError

        print(x)

    return inner

这个报错是因为 Python 在编译 inner 时发现有 x = ...,就默认 x 是 inner 的局部变量,可是右边的 x 又没定义,所以炸了。nonlocal 关键字就是用来解决这个问题的。它告诉 Python:别新建局部变量了,用外面那个现成的变量。这样我们就能在闭包里修改外部作用域的变量:

defouter():

    x = 10

    definner():

        nonlocal x

        x += 1

        print(x)

    return inner


f = outer()

f()  # 11

f()  # 12

现在 x 的值会跟着每次调用而累加,因为我们真的改了外部作用域里的那个 x。说到这里,你可能会问:既然闭包能一直保存变量,那它会不会导致内存泄漏啊?理论上是可能的,尤其是当闭包引用了很大的数据结构,而你又一直保留着闭包对象,这些数据就不会被释放。所以实际用的时候要注意,闭包虽然方便,但不是存储长期状态的银弹,滥用很容易变成一个隐形的内存杀手。另外一个有意思的细节是,闭包的这种“记忆”能力其实跟类的实例属性有点像。比如我们完全可以用一个类来模拟闭包:

classCounter:

    def__init__(self):

        self.x = 0

    definc(self):

        self.x += 1

        print(self.x)


c = Counter()

c.inc()  # 1

c.inc()  # 2

你看,这和用闭包保存状态的效果差不多。区别是类的方式显得更结构化,而闭包更轻便,没有额外的对象层级。再多提一句,nonlocal 和 global 不要混淆。global 是让你修改全局作用域的变量,而 nonlocal 只在闭包链条上找离当前函数最近的那个外部变量,不会跨到全局去。比如:

x = 0

defouter():

    x = 10

    definner():

        nonlocal x

        x += 1

        print(x)

    return inner


f = outer()

f()  # 11

这里的 nonlocal x 修改的是 outer 里的 x,全局的 x 完全没变。

从工程角度看,闭包最常见的用途就是做函数工厂(根据参数生成带有特定行为的函数)、状态保存(比如计数器)、装饰器(封装额外功能),这些场景用闭包会比写类更直观,也更符合函数式编程的味道。但闭包写多了也容易变成“晦涩代码制造机”,所以在团队协作时要确保别人能看懂,必要时加点注释,否则过两个月你自己回来看都懵。

我个人习惯是,如果状态很简单,就用闭包,代码简洁;如果状态复杂,还要处理多种行为,就老老实实用类,毕竟类的扩展性和可维护性更强。而 nonlocal 其实是个不常用的关键字,只有在你真的需要在闭包里修改外部变量时才会用,大多数闭包只读变量就够了。最后补一句,理解闭包和 nonlocal 不仅对写 Python 有用,对理解其他语言(比如 JavaScript、Swift、甚至 C 里的函数指针加上下文)都有帮助,因为这是跨语言的基础概念。只不过 Python 在语法上把它做得更显式,也给了我们像 nonlocal 这样的工具来控制作用域行为。

这么说下来,闭包其实并不神秘,就是个“带着小背包的函数”,里面装着它出生时的环境变量,nonlocal 则是帮你在背包里翻东西甚至换东西的钥匙,用好了非常方便。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1347 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2180 文章
  3. Pack 1347 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章