>>> L = [4, 5, 6]
>>> X = L * 4
>>> X
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6]
>>> L[1] = 0
为什么
>>> X
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6]
而不是
>>> X
[4, 0, 6, 4, 0, 6, 4, 0, 6, 4, 0, 6]
评估L * 4
时调用哪种函数或方法?
L.copy()
,list(L)
,copy.copy(L)
感谢。
答案 0 :(得分:4)
调用L * 4
时,调用的引擎下机制类似于以下Python代码:
>>> L = [4, 5, 6]
>>> N = 4
>>> new = [x for _ in range(N) for x in L] # i.e. L * N
>>> # Alternatively...
>>> new = []
>>> for _ in range(N):
... for x in L:
... new.append(x)
...
这意味着每个元素都是对原始元素的引用,但会创建一个新的列表对象。它与L.copy()
,list(L)
或copy.copy(L)
中的任何一个都不太相似。
让我们来看看这是如何在幕后实现的。以下是遇到*
运算符时CPython中发生的情况。:
TARGET(BINARY_MULTIPLY) {
PyObject *right = POP();
PyObject *left = TOP();
PyObject *res = PyNumber_Multiply(left, right);
Py_DECREF(left);
Py_DECREF(right);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
}
在PyNumber_Multiply
函数中,如果数字乘法失败,则假定其中一个对象是一个序列,因此最终(在一点间接之后)调用list_repeat
(这是一个过于简单化的,请参阅第一条评论更多细节)。这是代码
static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
Py_ssize_t i, j;
Py_ssize_t size;
PyListObject *np;
PyObject **p, **items;
PyObject *elem;
if (n < 0)
n = 0;
if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
return PyErr_NoMemory();
size = Py_SIZE(a) * n;
if (size == 0)
return PyList_New(0);
np = (PyListObject *) PyList_New(size); // <== Make a new list of the expanded size
if (np == NULL)
return NULL;
items = np->ob_item;
if (Py_SIZE(a) == 1) { // <== Optimization for L * 1
elem = a->ob_item[0];
for (i = 0; i < n; i++) {
items[i] = elem;
Py_INCREF(elem);
}
return (PyObject *) np;
}
p = np->ob_item;
items = a->ob_item;
for (i = 0; i < n; i++) { // <== Reference the original elements in the new list in the appropriate locations.
for (j = 0; j < Py_SIZE(a); j++) {
*p = items[j];
Py_INCREF(*p);
p++;
}
}
return (PyObject *) np;
}
如您所见,它首先分配一个新列表,然后将列表的每个对象引用到新列表中,而不是简单地创建原始的几个引用。因此,列表的每个元素都是对原始元素的引用,但列表本身不是对原始元素的引用。
答案 1 :(得分:1)
未修改X
的原因是
X = L * 4
是
的语法糖X = L.__mul__(4)
__mul__
函数是Python在使用*
运算符时调用的函数。它返回新对象:
>>> l = [1, 2]
>>> id(l.__mul__(4)) == id(l)
False
>>>
因此X
包含对L.__mul__(4)
返回的新对象的引用,而不是对引用的对象L
的引用。基本上,X
和L
引用完全不同的列表对象,因此更改一个变量引用不会影响另一个。
请注意,虽然Python不会复制引用L
,但它确实会创建对列表对象L
中引用的每个元素的引用副本。这就是为什么乘法子列表会导致意外行为:
>>> l = [[0]*2]*2
>>> l
[[0, 0], [0, 0]]
>>> l[0][0] = 1
>>> l
[[1, 0], [1, 0]]
>>>
答案 2 :(得分:1)
它 复制引用。参见:
In [14]: L = [4, 5, 6]
In [15]: fourL = 4*L
In [16]: [hex(id(i)) for i in L]
Out[16]: ['0x1002148d0', '0x1002148f0', '0x100214910']
In [17]: [hex(id(i)) for i in fourL]
Out[17]:
['0x1002148d0',
'0x1002148f0',
'0x100214910',
'0x1002148d0',
'0x1002148f0',
'0x100214910',
'0x1002148d0',
'0x1002148f0',
'0x100214910',
'0x1002148d0',
'0x1002148f0',
'0x100214910']