Нарізання кортежу не повертає новий об'єкт на відміну від нарізки списку


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

Відповіді:


13

Реалізації можуть повертати однакові екземпляри для непорушних типів (у CPython іноді можна побачити подібні оптимізації для рядків та цілих чисел). Оскільки об’єкт неможливо змінити, в коді користувача немає нічого, що повинно дбати, чи містить він унікальний екземпляр або просто інше посилання на існуючий екземпляр.

Ви можете знайти коротке замикання в коді С тут .

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
Віджай Джангір

2
Це може допомогти подивитися на типdef для кортежу тут . Так a->ob_item, як (*a).ob_item, тобто він отримує член, викликаний ob_itemвід того, на PyTupleObjectщо вказує a, і + ilow потім переходить до початку фрагмента.
Вім

3

Це деталізація реалізації. Оскільки списки є змінними, l1[:] потрібно створити копію, оскільки ви не очікуєте, що зміни l2вплинуть l1.

Оскільки кортеж є незмінним , ви нічого не можете зробити, щоб t2це вплинуло t1будь-яким видимим способом, тому компілятор вільний (але не обов'язковий ) використовувати той самий об’єкт для t1і t1[:].


1

У Python 3. * my_list[:]- синтаксичний цукор, type(my_list).__getitem__(mylist, slice_object)де: slice_object- об'єкт фрагмента, побудований з my_listатрибутів s (довжини) та виразу [:]. Об'єкти , які поводяться таким чином, називаються subscriptable в моделі даних 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 надає вам новий покажчик на той самий об’єкт, щоб уникнути копіювання, оскільки кортежі однакові (а оскільки об'єкт є кортежем, він незмінний).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.