My God! 偶然发现 Python 列表初始化中的巨坑

2019-04-05 15:46:19 +08:00
 mullenlee

table[0][1]=True在两种情况下竟然完全不同

第一种情况下,赋值之后,( 0,1 )为 True

第二种情况下,赋值之后,( 0,1)(1,1)整个第二列都为 True 了

哪位 Python 大佬能解释一下啊

2126 次点击
所在节点    问与答
9 条回复
cosmic
2019-04-05 15:52:57 +08:00
第二种方法只是复制了第一行的 reference,是 shallow copy。参考 fluent Python
alvin666
2019-04-05 15:55:06 +08:00
楼上说的对,第二种只是引用对象而已
mullenlee
2019-04-05 16:42:19 +08:00
@cosmic 谢谢
whoami9894
2019-04-05 17:30:31 +08:00
第二种方式初始化后里面四个 false 的地址都相同是同一个对象的引用,且布尔值是不可变对象所以你修改后会建立新的布尔值,这也是为什么修改第二个布尔值而第一个布尔值还是指向原先的 false。而 lst[0]本身是列表对象,所以修改 lst[0]会导致指向它的引用都会被修改。所以出现了[[0, 1], [0, 1]]这种情况
kidlj
2019-04-05 17:40:33 +08:00
shidenggui
2019-04-05 19:07:07 +08:00
最近在看 《 Python 源码剖析》
类似 [obj] * count 的表达式,编译成字节码之后为

```
# 构建 [obj]
0 LOAD_GLOBAL 0 (obj)
2 BUILD_LIST 1

# 读取 count
4 LOAD_GLOBAL 1 (count)

# 实现 [obj] * count
6 BINARY_MULTIPLY
```

而 BINARY_MULTIPLY 指令在源码中的实现为

```
TARGET(BINARY_MULTIPLY) {
PyObject *right = POP(); # 获取 count
PyObject *left = TOP(); # 获取 [obj]
# 获取相乘的结果
PyObject *res = PyNumber_Multiply(left, right);
Py_DECREF(left);
Py_DECREF(right);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
}
```
而 PyNumber_Multiply 最后调用的是 ->ob_type->tp_as_sequence->sq_repeat 函数,

```
# repeatfunc = sq_repeat
# seq = [obj]
# count = count
return (*repeatfunc)(seq, count);
```
该函数在 list 类型的实现为

```
static PySequenceMethods list_as_sequence = {
...
(ssizeargfunc)list_repeat, /* sq_repeat */
...
};
```

下面是 list_repeat 简化后的代码

```c
static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
...
PyObject *elem;
# np 指向新分配的 count 大小的 list 对象
np = (PyListObject *) PyList_New(size);

items = np->ob_item;

# 如果 [obj] 的对象大小为 1 的话,我们这里就是如此
if (Py_SIZE(a) == 1) {

# 获取 obj,即这里的 elem
elem = a->ob_item[0];

for (i = 0; i < n; i++) {
# 因为 elem 是指针,所以这里新的 list 的 items 里面包含的是原有的 obj 的引用,而不是复制
items[i] = elem;
Py_INCREF(elem);
}
return (PyObject *) np;
}
...
}
```

所以 [obj] * count 最后获得是包含 count 个 obj 引用的新 list

在 github 上写了一篇 blog,更加详细的论述了这个问题,有兴趣的可以点击观看

https://github.com/shidenggui/blog/issues/16
shidenggui
2019-04-05 19:08:54 +08:00
发现 v 站代码格式都混乱了,推荐去上条回复中的 github 链接查看
kmahyyg
2019-04-06 12:07:44 +08:00
浅拷贝问题 这是新手都会踩的坑, 我猜楼主没有看官方 docs, 官方 docs 里的 best practice 里特别说了这个地方
mullenlee
2019-04-19 21:42:27 +08:00
@kmahyyg 谢谢

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

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

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

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

© 2021 V2EX