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

Tuple slicing not returning a new object as opposed to list slicing

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

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 体验中,我的印象是空切片 returns 一个新对象。

我的理解是它返回同一个对象,因为元组是不可变的,没有必要创建它的新副本。但是同样,它在任何地方的文档中都没有提到。

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

由于元组是 不可变的,但是,您无法对 t2 做任何会以任何可见方式影响 t1 的事情,因此编译器可以免费(但 不需要 )为 t1t1[:].

使用相同的对象

对此不确定,但似乎 Python 为您提供了指向同一对象的新指针以避免复制,因为元组是相同的(并且由于对象是元组,所以它是不可变的)。

对于 不可变类型 的 return 相同实例的实现是免费的(在 CPython 中,您有时可能会看到对字符串和整数的类似优化)。由于无法更改该对象,因此用户代码中没有任何内容需要关心它是否持有唯一实例或只是对现有实例的另一个引用。

你可以在C代码中找到短路here

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 并不相同。

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

在 CPython 中,对于列表和元组,__getitem__ 由为元组 in here and for lists in here 实现的字节码操作 BINARY_SUBSCR 解释。

在元组的情况下,遍历代码你会看到在 this code block 中,static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item) 将 return 引用它得到的相同 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) 的代码并亲眼看到无论切片如何,新的列表对象总是 returned。