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 表达式这种比较隐蔽时)时,一定要注意闭包中对外部变量的引用是否在发生改变,要仔细思考这些改变是否符合预期

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

相关推荐
troy1289 分钟前
Codex 安全盲区:代码漏洞生成实测
windows·python·ci/cd·pycharm·django·github·fastapi
嵌入式学习菌34 分钟前
Workbuddy自动写一个RS485 / LoRa 参数调试工具
python
钱栈up1 小时前
番茄小说榜单爬虫失效排查:从页面路由变更到API参数映射的全流程复盘
python
唐璜Taro1 小时前
Agent Harness 系列 · 第 1 篇|Agent 不只是换一个更强的模型
人工智能·python
码行山野赴时序归途2 小时前
链表家族收官篇:循环链表
c语言·开发语言·数据结构·链表
泡茶喝茶写代码2 小时前
量化数据进阶:多维实战篇(第 11 篇):历史涨跌停价:涨跌停序列与止损线测算
java·python·股票数据api·股票数据·股票数据api接口·股票api数据接口·股票量化数据api
^Rocky2 小时前
IT 疑难杂症诊疗室:复杂技术问题解决实录
运维·bug
景熙55232 小时前
单体项目编写思路和落地形式
java·开发语言
ikun_文2 小时前
Django开启跨域请求
python·pycharm·django
外收内放2 小时前
Python与AI应用(项目开发实战:AI智能伴侣第三版)
python·学习·ai编程