Разрезание кортежей не возвращает новый объект, в отличие от разрезания списка

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 38557 ).
Вим

Ответы:

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
источник
Спасибо @wim. Это имеет смысл сейчас. Только одна вещь не по теме, так как у меня нет опыта в C. Что именно делает a-> ob_item? Я пытался найти это. но все, что я мог понять, это взять адрес «а» и переместить его «ob_item» вперед. Насколько я понимаю, ob_item содержит номер адреса хранения, который составляет «1». #offTheTopic
Виджай Джангир
2
Это может помочь посмотреть на typedef для кортежа, здесь . Так a->ob_item, как (*a).ob_item, например , он получает член , называемый ob_itemот , PyTupleObjectчто указывает на, и + ILOW переходит к началу среза.
Вим
3

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

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

chepner
источник
1

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

Фахер Мокадем
источник
1
Обратите внимание, что это отличается в 2.7 , где start:stopсрез встроенного типа, в том числе tup[:], не проходит BINARY_SUBSCR. Однако расширенная нарезка start:stop:stepпроходит подписку.
Вим
Хорошо, спасибо, обновлю, чтобы указать версию Python.
Фахер Мокадем
0

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

michotross
источник