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


12

在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经验中,我一直以为空切片会返回一个新对象为印象。

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



l2 = tuple(iter(l1))绕过优化
Chris_Rands

注意到您的问题后,c-api的PyTuple_GetSlice文档记录不正确。文档现已修复(这是bpo issue38557)。
wim

Answers:


13

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

您可以在此处的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不会做同样的事情。


谢谢@wim。现在这很有意义。因为我没有C的经验,所以这件事与主题无关。a-> ob_item到底是做什么的?我试图寻找它。但我所能理解的是它使用地址“ a”并将其“ ob_item”向前移动。我的理解是ob_item持有使“ 1”项目的存储地址的数量。#offTheTopic
Vijay Jangir

2
这里查看元组的typedef可能会有所帮助。所以a->ob_item(*a).ob_item,即它被调用的成员ob_itemPyTupleObject一个指向,而+ ILOW然后前进到片的开头。
WIM

3

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

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


1

在Python 3. * my_list[:]中,语法糖适用type(my_list).__getitem__(mylist, slice_object)于:slice_object是从my_list属性(length)和expression 构建的切片对象[:]。这种行为的对象在Python数据模型中称为可下标的,请参见此处。对于列表和元组__getitem__是一种内置方法。

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

如果是元组,则遍历代码,您将看到在此代码块中,如果item是类型并且切片求值为整个元组,static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)则将返回PyTupleObject对其作为输入参数的引用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)并亲自查看无论切片如何,始终会返回一个新的列表对象。


1
请注意,这与2.7不同,start:stop内置类型(包括)上的切片tup[:]不会通过BINARY_SUBSCR。不过,扩展切片start:stop:step的确需要进行订阅。
wim

好的,谢谢您将更新以指定python版本。
赫·莫卡登

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.