为什么重复列表不参考但复制?

时间:2017-07-12 04:14:01

标签: python python-3.x

>>> 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)
  • 还是其他什么?

感谢。

3 个答案:

答案 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的引用。基本上,XL引用完全不同的列表对象,因此更改一个变量引用不会影响另一个。

请注意,虽然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']