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

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

相关推荐
敏编程35 分钟前
一天一个Python库:jsonschema - JSON 数据验证利器
python
前端付豪42 分钟前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain
databook1 小时前
ManimCE v0.20.1 发布:LaTeX 渲染修复与动画稳定性提升
python·动效
花酒锄作田14 小时前
使用 pkgutil 实现动态插件系统
python
前端付豪17 小时前
LangChain链 写一篇完美推文?用SequencialChain链接不同的组件
人工智能·python·langchain
曲幽18 小时前
FastAPI实战:打造本地文生图接口,ollama+diffusers让AI绘画更听话
python·fastapi·web·cors·diffusers·lcm·ollama·dreamshaper8·txt2img
老赵全栈实战18 小时前
Pydantic配置管理最佳实践(一)
python
阿尔的代码屋1 天前
[大模型实战 07] 基于 LlamaIndex ReAct 框架手搓全自动博客监控 Agent
人工智能·python
AI探索者2 天前
LangGraph StateGraph 实战:状态机聊天机器人构建指南
python
AI探索者2 天前
LangGraph 入门:构建带记忆功能的天气查询 Agent
python