编程后端开发人员

解释一下 Python 中闭包是如何工作的,它们与普通函数有什么不同,以及它们的实际应用是什么?

用 Hintsage AI 助手通过面试

答案。

问题的历史

“闭包”这个术语是从函数式编程中借来的,早在 Python 出现之初就已存在。闭包允许函数记住它们创建时的环境,即使它们在外部环境中被调用。这个概念提供了灵活性,并允许实现多种模式,包括函数工厂和惰性计算。

问题

在 Python 中,函数是第一类对象。有时需要内部函数在外部函数完成后仍然使用 enclosing 作用域中的变量。普通的词法作用域在函数返回时并不保证这样的行为。如果这样的函数访问了它创建时的环境变量,就会产生闭包。

解决方案

当内函数引用外函数定义的变量,而外函数返回了内函数时,就会产生闭包。这通常用于创建函数工厂、在不使用类的情况下封装状态,以及构造具有“就地”参数的函数。

代码示例:

def make_multiplier(factor): def multiplier(x): return x * factor return multiplier mul2 = make_multiplier(2) mul3 = make_multiplier(3) print(mul2(10)) # 20 print(mul3(10)) # 30

关键特性:

  • 闭包保存环境变量的值,即使外部函数已经完成。
  • 内部函数中的状态本质上是私有的,无法直接在外部修改。
  • 为了在闭包中修改外部函数中的非不可变变量,使用关键字 nonlocal

可能的误区。

如果变量在内函数内部被修改,闭包能否在调用之间保持可变状态?

是的,如果在内函数中使用 nonlocal 关键字。如果没有 nonlocal,赋值会创建一个新的局部变量,而不更改外部变量。

def counter(): count = 0 def inc(): nonlocal count count += 1 return count return inc c = counter() print(c()) # 1 print(c()) # 2

在 Python 中,是否可以利用闭包实现私有变量,而不是使用类?

是的,闭包提供了一种简单的实现“私有”变量的方法,这些变量在没有内函数的 getter/setter 时对外不可及。

闭包是否只应用于函数?在 Python 中可以使用 lambda 函数构造闭包吗?

是的,闭包也可以由 lambda 表达式形成,因为它们在词法变量绑定方面与 def 相似。

def make_power(n): return lambda x: x ** n square = make_power(2) cube = make_power(3) print(square(4)) # 16 print(cube(2)) # 8

常见错误和反模式

  • 期待闭包在没有 nonlocal 的情况下自动更改外部变量。
  • 在闭包中捕获可变对象并改变它,而不考虑调试时的麻烦。
  • 使用循环在闭包中创建函数而没有正确绑定变量(“所有函数都看到最后一个变量值”的陷阱)。

现实生活中的例子

负面案例

一个在循环中形成处理程序的函数工厂在闭包中使用循环变量:

handlers = [] for i in range(3): def handler(x): return x + i handlers.append(handler) print([h(10) for h in handlers]) # [12, 12, 12]

优点:

  • 简单,代码少。

缺点:

  • 所有处理程序都引用同一个变量 i,它的最后值是 2 — 对大多数人来说这种行为是意料之外的。

正面案例

使用默认参数来“固定”值:

handlers = [] for i in range(3): def handler(x, j=i): return x + j handlers.append(handler) print([h(10) for h in handlers]) # [10, 11, 12]

优点:

  • 绑定所需的值。
  • 可预测的行为。

缺点:

  • 需要记住这个细节并手动修正闭包,这增加了代码的维护难度。