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

263 天前
 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

4936 次点击
所在节点    Python
55 条回复
darcyC
263 天前
你 print 的是当时那一刹那的值哦,你最后所谓的列表里的内容是最后 print 的哦,那也是那一刹那的值哦。
thinkershare
263 天前
这个根本就不是 bug ,就是可变对象的问题,你的 list 里面在最后的时候,有两个位置的元素引用了同一个对象。仅此而已。
llsquaer
263 天前
@darcyC 如果 print 的值是正确的,那么添加到 combinations 里的值应该也是按照 print 顺序进行添加的啊,再之后并没有做修改字典的动作了。
ktyang
263 天前
这是来钓鱼的嘛?
thinkershare
263 天前
@ktyang 感觉的确有钓鱼的嫌疑。
llsquaer
263 天前
@thinkershare 应该是对象引用问题,但是为啥 print 的值是对的?没错乱
thinkershare
263 天前
@llsquaer
因为状态随着时间的流逝被改变了(代码在线程上执行,已执行代码随时都可以改变内存中对象的状态).
一个对象在不同时刻,完全可以显示不同状态,print 是需要将对象转换为字符串(字符串序列化).
这个转化的时刻,会冻结那个时刻对象的字符串表示,而随着代码继续执行,这个对象被改变了。
print 打印时候首先要获得这个对象的字符串序列化表示,然后调用系统提供的接口将字符串使用指定字体在屏幕上渲染出来,因此一切都要看某一个时刻的状态。你不能对比不同时刻对一个对象状态(除非这个对象是不可变对象).
这个过程看似简单,实际还是涉及到很多乱七八糟的概念。
DOLLOR
263 天前
@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
263 天前
学学用调试器吧,愁
iintothewind
263 天前
写代码还是建议用不可变数据结构, 和无副作用的操作,
用可变数据结构和命令式操作, 你就需要对语句块生命周期内"操作的对象"的内部状态负责,
要不然就是自找麻烦.
Goooooos
263 天前
假设你列表里面只有一个元素,循环多少次更新都是同一个元素。

另外你把 combinations 改为 set 就明白了。
phrack
263 天前
mutable ,immutable 的区别,很常见的 python 问题。

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

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

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

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

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

© 2021 V2EX