列表字典中 随机一个字典增加 key ,再放入新列表中。出现预期不符。 直接上代码
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 问题在于示例里,单个 print 结果和添加到列表里的结果不一致.
python 版本 3.10.8
1
darcyC 196 天前
你 print 的是当时那一刹那的值哦,你最后所谓的列表里的内容是最后 print 的哦,那也是那一刹那的值哦。
|
2
thinkershare 196 天前
这个根本就不是 bug ,就是可变对象的问题,你的 list 里面在最后的时候,有两个位置的元素引用了同一个对象。仅此而已。
|
3
llsquaer OP @darcyC 如果 print 的值是正确的,那么添加到 combinations 里的值应该也是按照 print 顺序进行添加的啊,再之后并没有做修改字典的动作了。
|
4
ktyang 196 天前 1
这是来钓鱼的嘛?
|
5
thinkershare 196 天前 4
@ktyang 感觉的确有钓鱼的嫌疑。
|
6
llsquaer OP @thinkershare 应该是对象引用问题,但是为啥 print 的值是对的?没错乱
|
7
thinkershare 196 天前
@llsquaer
因为状态随着时间的流逝被改变了(代码在线程上执行,已执行代码随时都可以改变内存中对象的状态). 一个对象在不同时刻,完全可以显示不同状态,print 是需要将对象转换为字符串(字符串序列化). 这个转化的时刻,会冻结那个时刻对象的字符串表示,而随着代码继续执行,这个对象被改变了。 print 打印时候首先要获得这个对象的字符串序列化表示,然后调用系统提供的接口将字符串使用指定字体在屏幕上渲染出来,因此一切都要看某一个时刻的状态。你不能对比不同时刻对一个对象状态(除非这个对象是不可变对象). 这个过程看似简单,实际还是涉及到很多乱七八糟的概念。 |
8
DOLLOR 196 天前 2
@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': '李四'}] |
9
fatigue 196 天前 via iPhone 2
学学用调试器吧,愁
|
10
iintothewind 195 天前
写代码还是建议用不可变数据结构, 和无副作用的操作,
用可变数据结构和命令式操作, 你就需要对语句块生命周期内"操作的对象"的内部状态负责, 要不然就是自找麻烦. |
11
Goooooos 195 天前 via Android
假设你列表里面只有一个元素,循环多少次更新都是同一个元素。
另外你把 combinations 改为 set 就明白了。 |
12
phrack 195 天前 via iPhone 1
mutable ,immutable 的区别,很常见的 python 问题。
我也怀疑楼主钓鱼。 |
13
Marlon 195 天前
新手可能会遇到这个问题,理解可变对象和不可变对象就好了,类似于对象的引用。
|
14
Muniesa 195 天前 via Android
不是,你没发现 id38 被选了两次吗?第二次修改的时候会覆盖上一次的修改啊,你在循环里打印下 combinations 就知道咋回事了吧
|
15
shinession 195 天前
还好 OP 上代码了, 不然还真以为是啥 bug
|
16
lakitus 195 天前
test
|
17
lakitus 195 天前 1
这应该算是 python 中可变对象的原处修改这一块的知识,op 有时间可以把 python 里面的共享引用、驻留、对象拷贝机制(浅复制、深复制) 这一块的知识过一遍
|
18
zhtyytg 195 天前
钓鱼?
|
19
lsk569937453 195 天前 3
现在的人都这么自信了吗?代码不符合自己预期,一眼就是编程语言 bug.......
编程语言有 bug 吗?有。但不是一些新手能发现的。如果你发现程序不符合你的预期,首先应该是反思程序是不是有问题,或者拿给 chatGpt 解读一下也好,上来就是"发现一个编程语言 bug"。承包了我今天的笑料。 |
20
customsshen 195 天前
最后 print(aaa),看看结果就应该理解了
|
21
cyrivlclth 195 天前 1
钓鱼司马
|
22
anzu 195 天前 via iPhone
既然你觉得最后打印 combinations 的结果是错的,那么就应该也在 for 循环中打印 combinations 的值,观察其是怎么变化的。
|
23
FYFX 195 天前
你打印 ccc 的时候获得是当前 ccc.__repr__()的值,让后放到 combinations 里的 ccc 只是引用,后面修改了这个 ccc 之后再 print 的结果就是不一样啊
|
24
theprimone 195 天前
@phrack 大多数语言都有这个问题吧,有语言层面默认 immutable 的吗?
|
25
accelerator1 195 天前
进来之前就能猜到 LZ 要被群嘲了
|
26
superrichman 195 天前
把 id 打出来,你会发现其实有多个 id 一样的元素,他们指向同一个对象
print([id(x) for x in combinations]) 学一下 c 的指针就能理解了 |
27
Kinnice 195 天前 via Android 2
如果你不是某个语言的 Master ,那你遇到的不符合你的理解的现象,基本都是你的理解不到位.
|
28
InkStone 195 天前
Python 的作用域规则跟 C 不一样,cname 在出了 for 循环之后还是一个有效的对象,在下一次 for 循环中做的事情不是重新绑定了这个对象,而是修改了这个对象的值。
|
29
InkStone 195 天前
@theprimone Rust 呀
|
30
ck65 195 天前 via iPhone
一个观察不一定不准,发现了各个语言 bug 的新手,多半是来到了作用域的门前。
|
31
theprimone 195 天前
@InkStone #29 这样啊,写过 Rust 的 Hello World ,还不知道这么硬核呢
|
32
djangovcps 195 天前
能怀疑语言的内置容器有 bug ,我是没想到的
|
33
HashV2 195 天前
没有问题 循环内的打印对象在后续的循环过程中被修改了
|
34
mylifcc 195 天前
我是鱼
|
35
visper 195 天前
鱼,好大的鱼,虎纹鲨鱼
|
36
hooych 195 天前
代码执行的顺序并非是严格遵守代码逻辑的顺序,在不发生相关冲突的情况下,会发生顺序调整以优化性能。
|
37
1018ji 195 天前
好大的 bug
|
38
agegcn 195 天前
不符合预期就是 python 的 bug 。太自信了
|
39
CloveAndCurrant 195 天前
{'id': 38, 'src': 'xxx', 'cname': '张三-1'}、{'id': 38, 'src': 'xxx', 'cname': '张三-3'}这两个其实指向的是同一个字典,你更改一个,相当于都改了,字典的.copy()方法是浅拷贝,浅拷贝后就是指向不同的字典了。
|
40
Goooooos 195 天前 1
OP 可能真不适合编程。状态值都不懂。
|
41
Masterlxj 195 天前
因为 python 中列表和字典均为可变对象,列表内元素可变对象是引用,你 ID38 的对象存进去 2 次,两个元素指向的是同一个地址。for 循环中打印的是瞬时值,打印 combinations 是最终值。
|
42
jstony 195 天前
op 但凡把调试器打开看一眼都不会这么自信
|
43
tomczhen 195 天前
看到标题逐步 print 就能猜到内容了。
|
44
hxysnail 195 天前
这个行为很正常啊,指针或引用类型的数据都是这样的
有空可以了解一下语言的内部机制,你就会本能地避开某些机制性的坑,比如 Python 对象模型可以参考这个: https://fasionchan.com/python-source/object-model/overview |
45
iyaozhen 195 天前 1
这个对于编程新手来说绝对是个门槛
首先有个概念 dict/map 、list 是可变的,不管这个 map 放那里,你操作的都是它的指针(或者说是一个包含其内存地址的数据结构),简单理解类似 Windows 的快捷方式。不管你把这个 map 赋值给多少新的变量,都是复制了多个快捷方式 你肯定也知道 id:38 被随机选出来了两次,第二次 cname='张三-3',相当于修改了快捷方式对应的 map ,但往 combinations 里面放的都是其快捷方式。最后 print(combinations),就是拿着一个个快捷方式,去找对应的 map ,那当然 id:38 的 cname 都是'张三-3'了。.copy()嘛,则是复制原文件,而不是快捷方式了。 至于为什么要这样,就是为了节省内存。 更深入的话还可以看下 Copy On Write 机制 |
46
deplives 195 天前
建议先学习 c ,搞懂 指针相关的内容吧,
还有,别一上来就是语言的 bug 。多找找自己的原因。 |
47
Arrowing 195 天前
有没有可能一种可能,combinations 数组里的第二个和第四个的元素地址是一样的?
|
48
hellomsg 195 天前
跟 python 没关系,你换其他语言也一样。顺序执行已经很简单了,实在不行你把 for 拆开手动写成五条,再在脑子里运行一遍。
|
49
caiqichang 195 天前
![_20240510153226.png]( https://s2.loli.net/2024/05/10/Sc8u3gWt2lLanXp.jpg)
|
53
honjow 194 天前 via iPhone
看到标题就猜到大概是引用问题了。真就那么自信呗
|