python 闭包获取循环数据经典 bug

问题代码

python 复制代码
def create_functions():
    functions = []
    for i in range(3):
        # 创建一个函数,期望捕获当前循环的i值
        functions.append(lambda: print(f"My value is: {i}"))
    return functions

# 创建三个函数
f0, f1, f2 = create_functions()

# 调用这些函数
f0()  # 期望输出 "My value is: 0"
f1()  # 期望输出 "My value is: 1"
f2()  # 期望输出 "My value is: 2"

但是实际输出为

text 复制代码
My value is: 2
My value is: 2
My value is: 2

类似的,也可以不是用 lambda 表达式,而是使用函数实现闭包

python 复制代码
# 依旧有问题
def create_functions():
    functions = []
    for i in range(3):
        def func():
            print(f"My value is: {i}")
        functions.append(func)
    return functions

问题原因解释

产生这样问题的原因是:python 闭包捕获了同一个外部变量 i,并且是通过变量名 i 而非 i 的地址作为索引(这一点很关键,虽然实际要比这个复杂,但是可以理解为就是通过名称确定某个变量的!)

  • 如果不是通过变量 i 的字符串名字进行索引,也不会出现这个问题,实际上在 for i in range(3) 过程中给你,i 的地址是一直变的
  • 所以在最后 f0、f1、f2 都用过名字 i 来找内存,找到了最后的那个 2 对应的内存地址!

两种解决方案

方案 1:把值通过变量传进去,此时闭包引用的是 func 的局部变量 x,而每一个函数实际都是不同的

python 复制代码
def create_functions():
    functions = []
    for i in range(3):
        def func(x):
            return lambda: print(f"My value is: {x}")
        functions.append(func(i))
    return functions

# 创建三个函数
f0, f1, f2 = create_functions()

# 调用这些函数
f0()  # 期望输出 "My value is: 0"
f1()  # 期望输出 "My value is: 1"
f2()  # 期望输出 "My value is: 2"

方案 2:使用函数入参默认值,因为 python 在定义函数默认值时,需要计算出来(这也是另外一个经常出 bug 的问题)

python 复制代码
def create_functions():
    functions = []
    for i in range(3):
        def func(x=i):
            print(f"My value is: {x}")
        functions.append(func)
    return functions

# 创建三个函数
f0, f1, f2 = create_functions()

# 调用这些函数
f0()  # 输出 "My value is: 0"
f1()  # 输出 "My value is: 1"
f2()  # 输出 "My value is: 2"

总结

在产生闭包(尤其是 lambda 表达式这种比较隐蔽时)时,一定要注意闭包中对外部变量的引用是否在发生改变,要仔细思考这些改变是否符合预期

不过,只要知道原理,相信可以很好的处理这些情况

相关推荐
程序猿编码6 分钟前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
bkspiderx2 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
程序员老陆2 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光2 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
码云数智-园园2 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
禾小西3 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨3 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言
全栈练习生3 小时前
AI Agent 沙箱
python·ai
徐小黑ACG4 小时前
Golang 基础05 结构体struct
开发语言·算法·golang
优橙教育4 小时前
零基础学AI应用开发要多久?3个月能到什么水平
服务器·开发语言·网络·php