Krojenie krotkowe nie zwraca nowego obiektu w przeciwieństwie do krojenia listowego

12

W Pythonie (2 i 3). Ilekroć używamy wycinania list, zwraca nowy obiekt, np .:

l1 = [1,2,3,4]
print(id(l1))
l2 = l1[:]
print(id(l2))

Wynik

>>> 140344378384464
>>> 140344378387272

Jeśli to samo powtarza się z krotką, zwracany jest ten sam obiekt, np .:

t1 = (1,2,3,4)
t2 = t1[:]
print(id(t1))
print(id(t2))

Wynik

>>> 140344379214896
>>> 140344379214896

Byłoby wspaniale, gdyby ktoś mógł wyjaśnić, dlaczego tak się dzieje, przez całe moje doświadczenie w Pythonie miałem wrażenie, że pusty plasterek zwraca nowy obiekt.

Rozumiem, że zwraca ten sam obiekt, ponieważ krotki są niezmienne i nie ma sensu tworzyć nowej kopii. Ale znowu, nigdzie nie wspomniano w dokumentach.

Vijay Jangir
źródło
l2 = tuple(iter(l1))omija optymalizację
Chris_Rands
Zauważyłem, że c-apiPyTuple_GetSlice zostało udokumentowane niedokładnie po zobaczeniu twojego pytania. Dokumenty zostały teraz naprawione (był to błąd BPO38557 ).
wim

Odpowiedzi:

13

Implementacje mogą zwracać identyczne instancje dla typów niezmiennych (w CPython możesz czasem zobaczyć podobne optymalizacje dla ciągów i liczb całkowitych). Ponieważ obiekt nie może zostać zmieniony, w kodzie użytkownika nie ma nic, co musiałoby dbać o to, czy zawiera on unikalną instancję, czy tylko inne odniesienie do istniejącej instancji.

Zwarcie można znaleźć w kodzie C tutaj .

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 */
        }
    ...

Jest to szczegół implementacji, zauważ, że pypy nie robi tego samego.

wim
źródło
Dzięki @ wim. To ma teraz sens. Tylko jedna rzecz poza tematem, ponieważ nie mam doświadczenia w C. Co dokładnie robi a-> ob_item? Próbowałem go poszukać. ale wszystko, co mogłem zrozumieć, to to, że bierze adres „a” i przesuwa go „ob_item” do przodu. Zrozumiałem, że ob_item zawiera liczbę adresów pamięci, z których składa się pozycja „1”. #offTheTopic
Vijay Jangir
2
Pomocne może być spojrzenie na typedef dla krotki tutaj . Podobnie a->ob_itemjest (*a).ob_item, tzn. Pobiera element wywoływany ob_itemz elementu PyTupleObject, na który wskazuje a, a następnie znak + przechodzi do początku wycinka.
wim
3

To szczegół implementacji. Ponieważ listy są zmienne, l1[:] musisz utworzyć kopię, ponieważ nie spodziewałbyś się, że zmiany l2wpłyną l1.

Ponieważ krotka jest niezmienna , nic nie możesz zrobić, aby t2wpłynąć na nią t1w jakikolwiek widoczny sposób, więc kompilator może swobodnie (ale nie musi ) używać tego samego obiektu dla t1i t1[:].

chepner
źródło
1

W Pythonie 3. * my_list[:]jest cukrem składniowym, dla type(my_list).__getitem__(mylist, slice_object)którego: slice_objectjest obiektem wycinka zbudowanym z my_listatrybutów (długości) i wyrażenia [:]. Obiekty, które zachowują się w ten sposób, nazywane są subskrybowalnymi w modelu danych Python, patrz tutaj . W przypadku list i krotek __getitem__jest wbudowany metoda.

W CPython oraz dla list i krotek __getitem__jest interpretowany przez operację kodu bajtowego, BINARY_SUBSCRktóra jest implementowana dla krotek tutaj i dla list tutaj .

W przypadku krotek, spacery przez kod widać, że w tym bloku kodu , static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)powróci odniesienie do tego samego PyTupleObject, który dostał jako argumentu wejściowego, jeśli element jest typu PySlicei analizuje schemat plasterek całego krotki.

    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. */
        ...
}

Teraz sprawdzasz kod static PyObject * list_subscript(PyListObject* self, PyObject* item)i przekonujesz się, że niezależnie od wycinka zawsze zwracany jest nowy obiekt listy.

Fakher Mokadem
źródło
1
Zauważ, że jest inaczej w wersji 2.7 , gdzie start:stopwycinek wbudowanego typu, w tym tup[:], nie przechodzi BINARY_SUBSCR. Rozszerzone krojenie start:stop:stepprzechodzi jednak przez subskrypcję.
wim
OK, dzięki zaktualizujemy, aby określić wersję Pythona.
Fakher Mokadem
0

Nie jestem tego pewien, ale wydaje się, że Python zapewnia nowy wskaźnik do tego samego obiektu, aby uniknąć kopiowania, ponieważ krotki są identyczne (a ponieważ obiekt jest krotką, jest niezmienny).

michotross
źródło