Python 闭包的经典坑
flyfish
闭包不是 包,是函数 + 它记住的变量,英文是 Closure,本意是封闭、闭合。
闭包是Python中的嵌套函数,满足 内部函数引用外部函数的非全局变量 + 外部函数返回内部函数对,这个内部函数就是闭包。
闭包保留外部函数的状态,让函数拥有记忆性,无需依赖全局变量就能记住历史数据
cpp
def outer():
name = "小明" # 外部变量
# 这个 inner 就是闭包
def inner():
print(name) # 用了外部的变量
return inner
f = outer()
f() # 依然能打印出小明
自由变量
python
def outer(x):
# y:自由变量
y = 10
def inner():
# 内部函数只用了 x 和 y,没有自己定义
return x + y # x、y 都是自由变量
return inner
# 创建闭包
func = outer(5)
print(func()) # 15
x(外部函数参数)、y(外部函数变量)→ 都是自由变量
python
def make_functions():
func_list = []
for i in range(3):
# i 就是自由变量
def inner_func():
return i # 引用自由变量 i
func_list.append(inner_func)
return func_list
这里的 i 是自由变量,闭包(下面解释闭包)只记住了它的地址,没记住值,循环结束后i=2,所以所有闭包都返回2。
闭包:延迟绑定 vs 立即绑定
1. 什么是绑定?
闭包中,内部函数 和 外部自由变量 的关联过程 ,就叫绑定。
2. 什么是延迟绑定?闭包默认行为 → 坑
Python 闭包的默认规则 :
定义函数时 → 不绑定变量的值 ,只记住变量的引用(地址)
调用函数时 → 才去查找变量的最终值
这就是延迟绑定问题,也是闭包最大的坑。
3. 什么是立即绑」?(解决方案 → 正确)
定义函数时 → 立刻锁死变量的当前值
调用函数时 → 直接用锁死的值,不会变
这是解决延迟绑定坑。
错误示例:默认延迟绑定(坑)
需求
循环创建3个函数,分别返回 0、1、2
问题
延迟绑定 → 所有函数都返回循环的最终值 2
完整可运行代码
python
# 错误代码:闭包默认延迟绑定
def create_functions():
# 存放函数的列表
func_list = []
# 循环创建3个闭包函数
for i in range(3):
# 定义内部函数(闭包)
def inner():
# 引用自由变量 i
return i
# 把函数添加到列表
func_list.append(inner)
# 返回所有函数
return func_list
# ========== 测试(直接运行) ==========
if __name__ == "__main__":
# 获取3个函数
funcs = create_functions()
# 调用函数
print(funcs[0]()) # 预期0,实际2
print(funcs[1]()) # 预期1,实际2
print(funcs[2]()) # 预期2,实际2
错误原因
- 循环时,
inner函数只记住了变量i的引用,没记住值; - 循环结束后,
i的最终值是2; - 调用函数时,才去查
i的值 → 全是2。
正确示例1:函数默认参数(立即绑定)
原理
Python 函数的默认参数 ,会在定义函数时立即绑定值,永久锁死。
完整可运行代码
python
# 正确代码:默认参数实现立即绑定
def create_functions():
func_list = []
for i in range(3):
# 关键:num=i → 定义时立即绑定当前i的值
def inner(num=i):
return num
func_list.append(inner)
return func_list
# ========== 测试(直接运行) ==========
if __name__ == "__main__":
funcs = create_functions()
print(funcs[0]()) # 0
print(funcs[1]()) # 1
print(funcs[2]()) # 2
正确示例2:嵌套闭包(立即绑定)
原理
嵌套一层函数,立即传入循环变量,为每个函数创建独立的作用域,锁死值。
完整可运行代码
python
# 正确代码:嵌套闭包实现立即绑定
def create_functions():
func_list = []
# 嵌套一层:接收值,立即绑定
def wrapper(value):
def inner():
return value
return inner
# 循环时立即把i传入wrapper,锁死当前值
for i in range(3):
func_list.append(wrapper(i))
return func_list
# ========== 测试(直接运行) ==========
if __name__ == "__main__":
funcs = create_functions()
print(funcs[0]()) # 0
print(funcs[1]()) # 1
print(funcs[2]()) # 2
对比
| 绑定类型 | 触发时机 | 变量取值 | 结果 |
|---|---|---|---|
| 延迟绑定(默认) | 函数调用时 | 取变量最终值 | 错误 |
| 立即绑定(解决方案) | 函数定义时 | 取变量当前值(锁死) | 正确 |
使用lambda省代码
cpp
def create_functions():
func_list = []
for i in range(3):
# lambda: num=i → 定义时立即绑定当前 i 的值
func_list.append(lambda num=i: num)
return func_list
# 测试(直接运行)
if __name__ == "__main__":
funcs = create_functions()
print(funcs[0]()) # 0
print(funcs[1]()) # 1
print(funcs[2]()) # 2
造成闭包经典坑的原因:
闭包不会保存自由变量的值,只会保存变量的引用(内存地址);
Python 循环会复用同一个变量,且函数默认延迟绑定(调用时才取值),最终所有闭包都读取到了循环结束后的最终值。