发现一个 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

5365 次点击
所在节点    Python
55 条回复
darcyC
2024-05-10 00:15:09 +08:00
你 print 的是当时那一刹那的值哦,你最后所谓的列表里的内容是最后 print 的哦,那也是那一刹那的值哦。
thinkershare
2024-05-10 00:16:37 +08:00
这个根本就不是 bug ,就是可变对象的问题,你的 list 里面在最后的时候,有两个位置的元素引用了同一个对象。仅此而已。
llsquaer
2024-05-10 00:19:28 +08:00
@darcyC 如果 print 的值是正确的,那么添加到 combinations 里的值应该也是按照 print 顺序进行添加的啊,再之后并没有做修改字典的动作了。
ktyang
2024-05-10 00:20:12 +08:00
这是来钓鱼的嘛?
thinkershare
2024-05-10 00:21:30 +08:00
@ktyang 感觉的确有钓鱼的嫌疑。
llsquaer
2024-05-10 00:27:02 +08:00
@thinkershare 应该是对象引用问题,但是为啥 print 的值是对的?没错乱
thinkershare
2024-05-10 00:39:14 +08:00
@llsquaer
因为状态随着时间的流逝被改变了(代码在线程上执行,已执行代码随时都可以改变内存中对象的状态).
一个对象在不同时刻,完全可以显示不同状态,print 是需要将对象转换为字符串(字符串序列化).
这个转化的时刻,会冻结那个时刻对象的字符串表示,而随着代码继续执行,这个对象被改变了。
print 打印时候首先要获得这个对象的字符串序列化表示,然后调用系统提供的接口将字符串使用指定字体在屏幕上渲染出来,因此一切都要看某一个时刻的状态。你不能对比不同时刻对一个对象状态(除非这个对象是不可变对象).
这个过程看似简单,实际还是涉及到很多乱七八糟的概念。
AV1
2024-05-10 00:51:38 +08:00
@llsquaer
“再之后并没有做修改字典的动作了”——这话错了。
后面循环的时候,random.choice 仍会抽取到之前同一个 ccc ,然后 update 掉了。

你要明白一点,把 aaa 里的元素直接 append 到 combinations 里,combinations 的元素跟 aaa 的元素都是相同的引用。
任何对 aaa 元素的修改,都会影响到 combinations 里的元素。

类似的例子
list1 = [{'name': '张三'}]
list2 = []

# 抽取 list1 的元素,加入 list2
item = list1[0]
list2.append(item)

print(list1, list2) # 都是 [{'name': '张三'}]
item['name'] = '李四' # 修改了 list1 里的 item ,但 list2 里的也跟着变了
print(list1, list2) # 都是 [{'name': '李四'}]
fatigue
2024-05-10 00:56:42 +08:00
学学用调试器吧,愁
iintothewind
2024-05-10 06:59:02 +08:00
写代码还是建议用不可变数据结构, 和无副作用的操作,
用可变数据结构和命令式操作, 你就需要对语句块生命周期内"操作的对象"的内部状态负责,
要不然就是自找麻烦.
Goooooos
2024-05-10 07:17:45 +08:00
假设你列表里面只有一个元素,循环多少次更新都是同一个元素。

另外你把 combinations 改为 set 就明白了。
phrack
2024-05-10 07:47:32 +08:00
mutable ,immutable 的区别,很常见的 python 问题。

我也怀疑楼主钓鱼。
Marlon
2024-05-10 07:56:57 +08:00
新手可能会遇到这个问题,理解可变对象和不可变对象就好了,类似于对象的引用。
Muniesa
2024-05-10 08:00:47 +08:00
不是,你没发现 id38 被选了两次吗?第二次修改的时候会覆盖上一次的修改啊,你在循环里打印下 combinations 就知道咋回事了吧
shinession
2024-05-10 08:04:06 +08:00
还好 OP 上代码了, 不然还真以为是啥 bug
lakitus
2024-05-10 08:37:00 +08:00
test
lakitus
2024-05-10 08:39:31 +08:00
这应该算是 python 中可变对象的原处修改这一块的知识,op 有时间可以把 python 里面的共享引用、驻留、对象拷贝机制(浅复制、深复制) 这一块的知识过一遍
zhtyytg
2024-05-10 08:44:14 +08:00
钓鱼?
lsk569937453
2024-05-10 08:45:52 +08:00
现在的人都这么自信了吗?代码不符合自己预期,一眼就是编程语言 bug.......
编程语言有 bug 吗?有。但不是一些新手能发现的。如果你发现程序不符合你的预期,首先应该是反思程序是不是有问题,或者拿给 chatGpt 解读一下也好,上来就是"发现一个编程语言 bug"。承包了我今天的笑料。
customsshen
2024-05-10 08:48:52 +08:00
最后 print(aaa),看看结果就应该理解了

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

https://ex.noerr.eu.org/t/1039277

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

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

© 2021 V2EX