Beim Tupel-Slicing wird kein neues Objekt zurückgegeben, im Gegensatz zum Listen-Slicing

12

In Python (2 und 3). Immer wenn wir List Slicing verwenden, wird ein neues Objekt zurückgegeben, z.

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

Ausgabe

>>> 140344378384464
>>> 140344378387272

Wenn dasselbe mit Tupel wiederholt wird, wird dasselbe Objekt zurückgegeben, z.

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

Ausgabe

>>> 140344379214896
>>> 140344379214896

Es wäre großartig, wenn jemand etwas Licht ins Dunkel bringen könnte, warum dies geschieht. Während meiner gesamten Python-Erfahrung hatte ich den Eindruck, dass ein leeres Slice ein neues Objekt zurückgibt.

Mein Verständnis ist, dass es dasselbe Objekt zurückgibt, da Tupel unveränderlich sind und es keinen Sinn macht, eine neue Kopie davon zu erstellen. Aber auch hier wird es nirgendwo in den Dokumenten erwähnt.

Vijay Jangir
quelle
3
Verwandte Themen
Georgy
l2 = tuple(iter(l1))umgeht die Optimierung
Chris_Rands
Es wurde festgestellt, dass die c-api fürPyTuple_GetSlice ungenau dokumentiert wurde, nachdem Ihre Frage angezeigt wurde. Die Dokumente wurden jetzt behoben (dies war das bpo-Problem 38557 ).
wim

Antworten:

13

Implementierungen können identische Instanzen für unveränderliche Typen zurückgeben (in CPython werden manchmal ähnliche Optimierungen für Zeichenfolgen und Ganzzahlen angezeigt). Da das Objekt nicht geändert werden kann, muss im Benutzercode nichts berücksichtigt werden, ob es eine eindeutige Instanz oder nur einen anderen Verweis auf eine vorhandene Instanz enthält.

Den Kurzschluss finden Sie im C-Code hier .

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

Dies ist ein Implementierungsdetail. Beachten Sie, dass Pypy nicht dasselbe tut.

wim
quelle
Danke @wim. Das macht jetzt Sinn. Nur eine Sache außerhalb des Themas, da ich in C keine Erfahrung habe. Was genau macht a-> ob_item? Ich habe versucht, danach zu suchen. aber alles was ich verstehen konnte ist, dass es die Adresse von "a" nimmt und es "ob_item" vorwärts bewegt. Mein Verständnis war, dass ob_item die Nummer der Speicheradresse enthält, die das Element "1" ergibt. #offTheTopic
Vijay Jangir
2
Es könnte hilfreich sein, hier im typedef nach Tupel zu suchen . Es a->ob_itemist (*a).ob_itemalso so, als würde das Mitglied ob_itemvon dem aufgerufen, auf das PyTupleObjecta zeigt, und das + ilow rückt dann zum Anfang des Slice vor.
wim
3

Es ist ein Implementierungsdetail. Da Listen veränderbar sind, l1[:] muss eine Kopie erstellt werden, da Sie keine l2Auswirkungen erwarten würden l1.

Da ein Tupel jedoch unveränderlich ist , können Sie nichts tun, t2was sich auf t1sichtbare Weise auswirken würde. Daher kann der Compiler das gleiche Objekt für und verwenden (ist jedoch nicht erforderlich ) .t1t1[:]

chepner
quelle
1

In Python 3. * my_list[:]ist syntaktischer Zucker für type(my_list).__getitem__(mylist, slice_object)where: slice_objectist ein Slice-Objekt, das aus my_listden Attributen (Länge) und dem Ausdruck erstellt wird [:]. Objekte, die sich so verhalten, werden im Python-Datenmodell als subskriptierbar bezeichnet (siehe hier) . Für Listen und Tupel __getitem__ist eine integrierte Methode.

In CPython und für Listen und Tupel __getitem__wird dies durch die Bytecode-Operation interpretiert, die BINARY_SUBSCRfür Tupel hier und für Listen hier implementiert ist .

Im Fall von Tupeln, zu Fuß durch den Code sehen Sie , dass in diesem Codeblock , static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)einen Verweis auf den gleichen zurückgeben , PyTupleObjectdass sie als Eingabeargument bekommen, wenn Einzelteil vom TypPySlice und die Scheibe auswertet auf das gesamte Tupel.

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

Jetzt überprüfen Sie den Code static PyObject * list_subscript(PyListObject* self, PyObject* item)und stellen selbst fest, dass unabhängig vom Slice immer ein neues Listenobjekt zurückgegeben wird.

Fakher Mokadem
quelle
1
Beachten Sie, dass dies in 2.7 anders ist , wo ein start:stopSlice für den integrierten Typ, einschließlich tup[:], nicht übergeht BINARY_SUBSCR. Das erweiterte Slicing wird start:stop:stepjedoch abonniert.
wim
Okay, danke wird aktualisiert, um die Python-Version anzugeben.
Fakher Mokadem
0

Ich bin mir nicht sicher, aber es scheint, dass Python Ihnen einen neuen Zeiger auf dasselbe Objekt bietet, um das Kopieren zu vermeiden, da die Tupel identisch sind (und da das Objekt ein Tupel ist, ist es unveränderlich).

michotross
quelle