小能豆

Python 闭包的工作原理

py

我试图了解闭包在 Python 中的工作原理,我发现了以下代码片段:

def closure():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3

我可以理解这段代码,因为当inner定义函数时,封闭范围有一个名为 count 的变量,其值为 0,然后内部函数将remember这个值

但是,如果我移动到count = 0内部函数下面,那么代码就变成这样:

def closure():
    def inner():
        nonlocal count
        count += 1
        print(count)
    count = 0
    return inner

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3

令我惊讶的是,代码仍然运行良好,这真的让我很困惑。因为在inner定义时,变量count并不存在于封闭范围内,inner函数如何能够记住此时命名空间中尚不存在的值?

是不是因为类似JS的变量提升的东西在Python中也存在?


阅读 8

收藏
2024-11-14

共1个答案

小能豆

这是一个很好的问题!在 Python 中,闭包的工作原理可能会让人感到困惑,尤其是当涉及到变量定义顺序时。让我们逐步分析这个问题。

闭包概述

首先,回顾一下什么是 闭包(Closure)。在 Python 中,闭包是指一个函数能够“记住”并访问它在定义时的环境(即变量)。它的关键是内部函数(如 inner)能够访问外部函数(如 closure)的变量,即使外部函数已经结束执行。

在你的第一个代码片段中:

def closure():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3
  • closure 函数定义了一个变量 count,并返回了 inner 函数。
  • inner 函数可以访问 closure 函数中的变量 count,因为它是一个闭包。
  • 使用 nonlocal count 关键字,inner 函数可以修改 count 的值。

变量定义顺序

接下来,你将 count = 0 放到 inner 函数之后:

def closure():
    def inner():
        nonlocal count
        count += 1
        print(count)
    count = 0
    return inner

你认为这会出错,因为在 inner 函数定义时,count 还没有被初始化。然而,Python 运行时并没有报错,代码依然能够按预期工作。为什么会这样呢?

Python 中的作用域与赋值

这是因为 Python 中 作用域的解析规则 与 JavaScript 中的 “提升”(hoisting)有些不同,但也有相似之处。

  • 在 Python 中,函数的 作用域 是在函数定义时就确定的。
  • inner 函数被定义时,它的作用域(即闭包的外部环境)已经固定下来。也就是说,inner 已经“捕获”了 count 变量所在的作用域,而不仅仅是在它被调用时的作用域。
  • closure 函数执行时,count 被赋值为 0,然后 inner 被返回,并在后续的调用中使用了它。

在你的代码中,虽然 count 的赋值发生在 inner 函数定义之后,但实际上 Python 确保了 count 作为 inner 闭包的外部变量存在,而这个变量的初始值是在 closure 被调用时设置的。

因此,count 变量实际上是随着 closure 函数的执行被创建的,并且它在 inner 被调用时已经存在,并被 nonlocal 关键字修改。

与 JavaScript 的变量提升的区别

在 JavaScript 中,变量声明(使用 var)会被“提升”,即使它们出现在函数体的后面,也会被视为在函数的顶部定义。而在 Python 中,并没有这种“提升”的机制。

在你的例子中,虽然 countinner 函数定义之前没有赋值,但 Python 保证 count 会作为外部环境的一部分,允许 inner 引用它,这并不是因为“提升”机制,而是因为闭包的环境已在函数定义时就被绑定。

总结

  • Python 中的闭包:即使 count 的赋值发生在 inner 函数定义之后,Python 也会允许 inner 函数引用 count,因为在 closure 函数执行时,inner 函数会捕获 count 变量所在的作用域。
  • 与 JavaScript 提升的区别:Python 不支持变量声明的提升。闭包的行为与作用域解析规则密切相关,countclosure 执行时就被定义并可以被 inner 捕获。

这个行为在 Python 中并不是由于变量提升,而是因为闭包捕获外部函数的环境和作用域,因此你可以在 inner 中引用 count,即使它的赋值出现在 inner 定义之后。

2024-11-14