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