闭包是 Python 函数进阶中的一个重要概念,它让函数能够“记住”定义时的环境。理解闭包,对于编写装饰器、回调函数和封装状态的代码至关重要。
闭包的形成条件
一个闭包的形成,需要同时满足以下三个条件:
- 存在嵌套函数:在一个外部函数内部,定义了另一个内部函数。
- 内部函数引用了外部函数的局部变量(这些变量称为自由变量)。
- 外部函数将内部函数作为返回值返回。
只有内部函数被返回到外部作用域时,它才可能被其他地方调用,从而让闭包真正“闭合”外部变量。如果内部函数仅在外部函数内部调用,那它只是一个普通的嵌套函数,算不上闭包。
简单示例:
def outer(message):
def inner():
print(f"记住的信息: {message}")
return inner
closure_func = outer("你好")
closure_func() # 输出:记住的信息: 你好
这里 inner 引用了 outer 的局部变量 message,并且 outer 返回了 inner,所以 inner 就是一个闭包。
变量保存机制
闭包之所以能“记住”外部变量,是因为 Python 在创建内部函数时,会在函数对象的 closure 属性中保存对外部变量引用的单元格对象(cell object)。
具体过程:
- 当 Python 编译遇到嵌套函数,且内部函数引用了外部函数的变量时,会为这些变量创建一个
cell对象。 - 外部函数执行时,这些变量不再存储为普通的局部变量,而是被放入
cell对象中,从而实现值的“共享”和“持久化”。 - 内部函数的
closure属性会记录这些cell对象,即使外部函数执行完毕、其局部命名空间已经销毁,通过closure仍然可以访问到当时的变量值。
验证代码:
def outer(x):
y = 10
def inner():
return x + y
return inner
f = outer(5)
print(f.__closure__) # (<cell at 0x...: int object at ...>, <cell at 0x...: int object at ...>)
print(f.__closure__[0].cell_contents) # 5
print(f.__closure__[1].cell_contents) # 10
print(f()) # 15
即便 outer 函数已经返回,我们仍能通过闭包访问到 x 和 y 的值。
常见陷阱:循环中的闭包
闭包保存的是变量本身的引用,而不是值的副本。如果在循环中创建多个闭包,它们都引用同一个循环变量,最终所有闭包看到的都是循环结束后的值。
funcs = []
for i in range(3):
def inner():
return i
funcs.append(inner)
print([f() for f in funcs]) # 输出: [2, 2, 2],而不是 [0, 1, 2]
解决方法:在每次迭代时用一个默认参数来“快照”当前值:
funcs = []
for i in range(3):
def inner(i=i):
return i
funcs.append(inner)
print([f() for f in funcs]) # 输出: [0, 1, 2]
或者再包一层函数来创建独立的变量作用域。
闭包的实际用途
- 实现装饰器(带参数的装饰器核心就是闭包)。
- 创建带有状态的简单函数对象,替代只有一两个方法的类。
- 回调函数或事件处理中保存上下文数据。
- 在函数式编程中实现柯里化、偏函数等模式。
闭包是 Python 函数式编程的基石,掌握它的形成条件和变量保存机制,写装饰器和理解高阶函数会变得非常自然。