python - 与列表切片相反,元组切片不返回新对象




list tuples (3)

在Python 3 *中, my_list[:]type(my_list).__getitem__(mylist, slice_object) 语法糖,其中: slice_object 是根据 my_list 的属性(长度)和表达式 [:] 构建的切片对象。 这种行为的对象在Python数据模型中称为可下标的,请参见 here 。 对于列表和元组, __getitem__ 是内置方法。

在CPython中,对于列表和元组, BINARY_SUBSCR 由字节码操作 BINARY_SUBSCR 解释,该操作针对 此处的 元组 和此处 的列表 实现

如果是元组,则遍历代码,您将看到在 此代码块中 static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item) 将返回对作为输入参数的相同 PyTupleObject 的引用,如果item为键入 PySlice ,然后切片将求出整个元组。

    static PyObject*
    tuplesubscript(PyTupleObject* self, PyObject* item)
    {
        /* checks if item is an index */ 
        if (PyIndex_Check(item)) { 
            ...
        }
        /* else it is a slice */ 
        else if (PySlice_Check(item)) { 
            ...
        /* unpacks the slice into start, stop and step */ 
        if (PySlice_Unpack(item, &start, &stop, &step) < 0) { 
            return NULL;
        }
       ...
        }
        /* if we start at 0, step by 1 and end by the end of the tuple then !! look down */
        else if (start == 0 && step == 1 &&
                 slicelength == PyTuple_GET_SIZE(self) && 
                 PyTuple_CheckExact(self)) {
            Py_INCREF(self); /* increase the reference count for the tuple */
            return (PyObject *)self; /* and return a reference to the same tuple. */
        ...
}

现在,您检查 static PyObject * list_subscript(PyListObject* self, PyObject* item) 的代码,并亲自检查无论切片如何,始终会返回一个新的列表对象。

在Python(2和3)中。 每当我们使用列表切片时,它都会返回一个新对象,例如:

l1 = [1,2,3,4]
print(id(l1))
l2 = l1[:]
print(id(l2))

输出量

>>> 140344378384464
>>> 140344378387272

如果使用元组重复相同的事情,则返回相同的对象,例如:

t1 = (1,2,3,4)
t2 = t1[:]
print(id(t1))
print(id(t2))

输出量

>>> 140344379214896
>>> 140344379214896

如果有人能弄清为什么会发生,那将是很棒的,在我的整个Python经验中,我一直以为空切片会返回一个新对象的印象。

我的理解是,它返回的对象与元组是不可变的相同,因此没有必要为其创建新副本。 但是同样,文档中也没有提到它。


实现可以自由地为 不变类型 返回相同的实例(在CPython中,您有时可能会看到对字符串和整数的类似优化)。 由于无法更改对象,因此用户代码中没有任何内容需要关心它是保存唯一实例还是仅是对现有实例的另一个引用。

您可以在 here 的C代码中找到短路。

static PyObject*
tuplesubscript(PyTupleObject* self, PyObject* item)
{
    ... /* note: irrelevant parts snipped out */
    if (start == 0 && step == 1 &&
                 slicelength == PyTuple_GET_SIZE(self) &&
                 PyTuple_CheckExact(self)) {
            Py_INCREF(self);          /* <--- increase reference count */
            return (PyObject *)self;  /* <--- return another pointer to same */
        }
    ...

这是一个实现细节,请注意 pypy 不会做同样的事情。


这是一个实现细节。 因为列表是可变的,所以 l1[:] 必须 创建一个副本,因为您不会期望对 l2 更改会影响 l1

但是,由于元组是 不可变的 ,因此您无法对 t2 做任何以任何可见的方式影响 t1 事情,因此编译器可以自由(但不是 必需 )为 t1t1[:] 使用相同的对象。





cpython