编程Python开发者

闭包(closures)在Python中是如何工作的?访问外部函数变量的特点是什么,以及如何避免与可变变量相关的陷阱?

用 Hintsage AI 助手通过面试

答案。

闭包(closures)是Python中的一种强大机制,允许创建带有来自周围上下文的“记忆”数据的函数。

问题的背景

在函数式编程语言和Python中,函数是第一类对象。函数可以返回一个新函数,该函数会“闭合”其外部作用域中的变量。

问题

开发人员经常混淆闭合变量在closure内部是如何“存在”的,何时读取或写入它们,以及如何安全地处理它们(特别是与可变对象相关时)。

解决方案

如果内部函数使用外部函数的变量,它们会自动“被记住”——即使外部函数已经完成工作。对于读取变量这一点很明显,但如果需要更改值——就需要使用关键字nonlocal。在处理可变对象(例如lists, dicts)时,这尤其是一个风险区域。

示例:

def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2

关键特点:

  • 嵌套函数记住在其创建时的可见变量的值。
  • 要更改闭合变量,必须使用nonlocal(否则此操作会创建一个局部变量)。
  • 闭包中的对象引用即使在退出外部函数后也会保存,这允许实现函数工厂、词法计数器等。

陷阱性问题。

可以在函数内部不使用nonlocal修改闭合变量的值吗?

不可以。如果尝试在没有指定nonlocal的情况下分配新值,Python会将其视为创建一个新的局部变量,而旧值不会被改变。

示例:

def make_counter(): count = 0 def inner(): count += 1 # 错误 UnboundLocalError! return count return inner

可以通过closure将参数传递到外部作用域吗?

可以,closure会“记住”所有可在外部作用域中访问的变量,包括类的属性、全局变量等。但更改这些变量需要特别的努力(例如使用nonlocal或global)。

在closure内部,关于可变对象发生了什么?

如果闭合了一个引用可变对象的变量,例如列表,您可以在不使用nonlocal的情况下修改其内容,但如果您尝试重新赋值变量——就需要使用nonlocal。

示例:

def make_appender(): result = [] def append(x): result.append(x) # 可以! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]

常见错误和反模式

  • 尝试在没有nonlocal的情况下重新赋值closure中的变量。
  • 使用closure来存储可变状态,而未意识到可能的内存泄漏。
  • 由于闭合变量过多而导致的代码难以阅读。

实际案例

消极案例

开发人员编写闭包,在没有nonlocal的情况下更改变量——导致UnboundLocalError。

优点:

  • 快速原型设计计数器、工厂。

缺点:

  • 不可预测的行为,错误使用nonlocal时可能出现bug。

积极案例

在closure中明确使用nonlocal以管理状态。

优点:

  • 清晰可控的状态,容易实现计数器和函数工厂。

缺点:

  • 更难理解和调试大链条的closure。