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 的地址是一直变的
  • 所以在最后 f0f1f2 都用过名字 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 表达式这种比较隐蔽时)时,一定要注意闭包中对外部变量的引用是否在发生改变,要仔细思考这些改变是否符合预期

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

相关推荐
m0_733565461 分钟前
bootstrap怎么实现响应式的文章瀑布流布局
jvm·数据库·python
m0_4636722010 分钟前
Golang如何用火焰图分析性能_Golang火焰图教程【对比】
jvm·数据库·python
knight_9___22 分钟前
大模型project面试4
人工智能·python·深度学习·算法·面试·agent
m0_5913647325 分钟前
Go语言怎么做链路追踪_Go语言分布式链路追踪教程【精选】
jvm·数据库·python
m0_4636722026 分钟前
HTML函数工具是否支持雷蛇等游戏外设_RGB同步汇总【汇总】
jvm·数据库·python
谙弆悕博士27 分钟前
【附C源码】二叉搜索树的C语言实现
c语言·开发语言·数据结构·算法·二叉树·项目实战·数据结构与算法
C+++Python29 分钟前
C++ 泛型编程 极简示例代码
开发语言·c++
zkkkkkkkkkkkkk32 分钟前
python使用celery实现异步任务
redis·python·rabbitmq·rocketmq
Rust研习社36 分钟前
Ubuntu 全面拥抱 Rust 后,我意识到 Rust 社区要变了
linux·服务器·开发语言·后端·ubuntu·rust
宵时待雨40 分钟前
回溯算法专题2:二叉树中的深搜
开发语言·数据结构·c++·笔记·算法·深度优先