Разрезание кортежей не возвращает новый объект, в отличие от разрезания списка - PullRequest
11 голосов
/ 22 октября 2019

В 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 у меня было впечатление, что пустой фрагмент возвращает новый объект.

Насколько я понимаю, он возвращает тот же объект, поскольку кортежи неизменны, и нет смысла создавать его новую копию. Но опять же, это нигде не упоминается в документах.

Ответы [ 4 ]

13 голосов
/ 22 октября 2019

Реализации могут возвращать идентичные экземпляры для неизменяемых типов (в 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 не делает то же самое.

3 голосов
/ 22 октября 2019

Это деталь реализации. Поскольку списки являются изменяемыми, l1[:] должен создать копию, поскольку вы не ожидаете, что изменения в l2 коснутся l1.

Поскольку кортеж является неизменным, однако, вы ничего не можете сделать с t2, который мог бы повлиять на t1 любым видимым образом, поэтому компилятор свободен (но не требуется ), чтобы использовать тот же объект для t1 и t1[:].

1 голос
/ 22 октября 2019

В Python 3. * my_list[:] - синтаксический сахар для type(my_list).__getitem__(mylist, slice_object), где: slice_object - объект среза, построенный из атрибутов (длины) my_list и выражения [:]. Объекты, которые ведут себя таким образом, называются подписанными в модели данных Python, см. здесь . Для списков и кортежей __getitem__ - это встроенный метод.

В CPython и для списков и кортежей __getitem__ интерпретируется операцией байт-кода BINARY_SUBSCR, которая реализована для кортежей здесь и для списков здесь .

В случае кортежей, проходя по коду, вы увидите, что в этот блок кода , 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) и убедитесь, что независимо от фрагмента, всегда возвращается новый объект списка.

0 голосов
/ 22 октября 2019

Не уверен насчет этого, но похоже, что Python предоставляет вам новый указатель на тот же объект, чтобы избежать копирования, так как кортежи идентичны (и поскольку объект является кортежем, он неизменен).

...