发现一个 Python bug,最初以为是引用问题,后来逐步 print 看还真是 bug

2024-05-10 00:10:51 +08:00
 llsquaer

列表字典中 随机一个字典增加 key ,再放入新列表中。出现预期不符。 直接上代码

有 bug 的情况

aaa = [
    {'id': 35,'src':'xxx'},
    {'id': 36,'src':'xxx'},
    {'id': 37,'src':'xxx'},
    {'id': 38,'src':'xxx'},
]

combinations = []

for i in range(5):
    cname = f'张三-{i}'
    ccc = random.choice(aaa)
    ccc.update({'cname': cname})
    print(ccc)                  # 这里的结果符合预期

    combinations.append(ccc)

print(combinations)             # 但是这里就错了

返回结果

{'id': 37, 'src': 'xxx', 'cname': '张三-0'}
{'id': 38, 'src': 'xxx', 'cname': '张三-1'}
{'id': 35, 'src': 'xxx', 'cname': '张三-2'}
{'id': 38, 'src': 'xxx', 'cname': '张三-3'}
{'id': 36, 'src': 'xxx', 'cname': '张三-4'}
# 以上 print 结果是对的

[{'id': 37, 'src': 'xxx', 'cname': '张三-0'}, {'id': 38, 'src': 'xxx', 'cname': '张三-3'}, {'id': 35, 'src': 'xxx', 'cname': '张三-2'}, {'id': 38, 'src': 'xxx', 'cname': '张三-3'}, {'id': 36, 'src': 'xxx', 'cname': '张三-4'}]
# 但是这里打印新生成的 combinations 列表就出现两个 `张三-3` 

改代码

后来想起来是引用对象问题,需要浅复制下.即只需要将 ccc = random.choice(aaa)改为ccc = random.choice(aaa).copy() 就符合预期了.

bug 的疑问

bug 问题在于示例里,单个 print 结果和添加到列表里的结果不一致.

python 版本 3.10.8

5386 次点击
所在节点    Python
55 条回复
Masterlxj
2024-05-10 11:22:52 +08:00
因为 python 中列表和字典均为可变对象,列表内元素可变对象是引用,你 ID38 的对象存进去 2 次,两个元素指向的是同一个地址。for 循环中打印的是瞬时值,打印 combinations 是最终值。
jstony
2024-05-10 11:28:11 +08:00
op 但凡把调试器打开看一眼都不会这么自信
tomczhen
2024-05-10 12:55:27 +08:00
看到标题逐步 print 就能猜到内容了。
hxysnail
2024-05-10 13:19:26 +08:00
这个行为很正常啊,指针或引用类型的数据都是这样的

有空可以了解一下语言的内部机制,你就会本能地避开某些机制性的坑,比如 Python 对象模型可以参考这个:

https://fasionchan.com/python-source/object-model/overview
iyaozhen
2024-05-10 13:43:31 +08:00
这个对于编程新手来说绝对是个门槛

首先有个概念 dict/map 、list 是可变的,不管这个 map 放那里,你操作的都是它的指针(或者说是一个包含其内存地址的数据结构),简单理解类似 Windows 的快捷方式。不管你把这个 map 赋值给多少新的变量,都是复制了多个快捷方式

你肯定也知道 id:38 被随机选出来了两次,第二次 cname='张三-3',相当于修改了快捷方式对应的 map ,但往 combinations 里面放的都是其快捷方式。最后 print(combinations),就是拿着一个个快捷方式,去找对应的 map ,那当然 id:38 的 cname 都是'张三-3'了。.copy()嘛,则是复制原文件,而不是快捷方式了。

至于为什么要这样,就是为了节省内存。

更深入的话还可以看下 Copy On Write 机制
deplives
2024-05-10 13:50:13 +08:00
建议先学习 c ,搞懂 指针相关的内容吧,
还有,别一上来就是语言的 bug 。多找找自己的原因。
Arrowing
2024-05-10 14:26:19 +08:00
有没有可能一种可能,combinations 数组里的第二个和第四个的元素地址是一样的?
hellomsg
2024-05-10 15:00:41 +08:00
跟 python 没关系,你换其他语言也一样。顺序执行已经很简单了,实在不行你把 for 拆开手动写成五条,再在脑子里运行一遍。
caiqichang
2024-05-10 15:38:59 +08:00
caiqichang
2024-05-10 15:43:40 +08:00
Cu635
2024-05-10 15:49:46 +08:00
@iyaozhen #45
感觉 python 的这个特性还不如 C 语言的指针呢,好歹 c 语言指针是显式的,这种隐蔽的太坑人了。
krixaar
2024-05-10 16:26:10 +08:00
@Cu635 #51 该把 VB 那套 ByVal ByRef 学过来🤣
honjow
2024-05-11 03:31:04 +08:00
看到标题就猜到大概是引用问题了。真就那么自信呗
honjow
2024-05-11 03:36:45 +08:00
@Goooooos 不懂问题倒不大,好好学就行,反而是这种不符合自己预期结果就说是语言 bug 的态度才是大问题
llsquaer
2024-05-11 09:28:58 +08:00
@DOLLOR 是当时脑子犯怵啦。 在循环里,上一次的引用对象,被下一次循环 update 更改了。。当时没转过弯,老是死磕 print 去了。

还有一个是当时标题写的夸张点了,今天回来还在想为啥会取一个这个标题。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1039277

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX