Tuple slicing non restituisce un nuovo oggetto invece di list slicing


12

In Python (2 e 3). Ogni volta che utilizziamo la suddivisione in elenchi restituisce un nuovo oggetto, ad esempio:

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

Produzione

>>> 140344378384464
>>> 140344378387272

Se la stessa cosa si ripete con la tupla, viene restituito lo stesso oggetto, ad es .:

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

Produzione

>>> 140344379214896
>>> 140344379214896

Sarebbe bello se qualcuno potesse far luce sul perché ciò sta accadendo, durante la mia esperienza con Python ero sotto l'impressione che la fetta vuota restituisse un nuovo oggetto.

La mia comprensione è che sta restituendo lo stesso oggetto in quanto le tuple sono immutabili e non ha senso crearne una nuova copia. Ma ancora una volta, non è menzionato nei documenti da nessuna parte.



l2 = tuple(iter(l1))ignora l'ottimizzazione
Chris_Rands

Ho notato che c-api èPyTuple_GetSlice stato documentato in modo impreciso dopo aver visto la tua domanda. I documenti sono stati corretti (questo era il problema bpo38557 ).
mercoledì

Risposte:


13

Le implementazioni sono libere di restituire istanze identiche per tipi immutabili (in CPython, a volte potresti vedere ottimizzazioni simili per stringhe e numeri interi). Poiché l'oggetto non può essere modificato, nel codice utente non è necessario preoccuparsi se contiene un'istanza univoca o solo un altro riferimento a un'istanza esistente.

Puoi trovare il corto circuito nel codice C qui .

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

Questo è un dettaglio di implementazione, nota che pypy non fa lo stesso.


Grazie @wim. Questo ha senso ora. Solo una cosa fuori tema perché non ho esperienza in C. Che cosa fa esattamente un-> ob_item? Ho provato a cercarlo. ma tutto quello che ho potuto capire è che prende l'indirizzo di "a" e lo sposta in avanti "ob_item". La mia comprensione era ob_item contiene il numero di indirizzo di archiviazione che rende "1" elemento. #offTheTopic
Vijay Jangir,

2
Potrebbe aiutare a guardare il typedef per tupla, qui . Quindi a->ob_itemè come (*a).ob_item, cioè ottiene il membro chiamato ob_itemda quello a PyTupleObjectche punta, e il + + quindi avanza all'inizio della sezione.
mercoledì

3

È un dettaglio di implementazione. Poiché gli elenchi sono modificabili, è l1[:] necessario crearne una copia, poiché non ci si aspetterebbe che le modifiche l2influiscano l1.

Poiché una tupla è immutabile , tuttavia, non c'è nulla che tu possa fare t2che possa influire t1in modo visibile, quindi il compilatore è libero (ma non necessario ) di usare lo stesso oggetto per t1e t1[:].


1

In Python 3. * my_list[:]è lo zucchero sintattico per type(my_list).__getitem__(mylist, slice_object)dove: slice_objectè un oggetto slice creato dagli my_listattributi (lunghezza) e dall'espressione [:]. Gli oggetti che si comportano in questo modo sono chiamati sottoscrivibili nel modello di dati Python, vedere qui . Per elenchi e tuple __getitem__è un metodo integrato.

In CPython, e per elenchi e tuple, __getitem__è interpretato dall'operazione bytecode BINARY_SUBSCRche è implementata per le tuple qui e per gli elenchi qui .

In caso di tuple, camminando attraverso il codice vedrai che in questo blocco di codice , static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)verrà restituito un riferimento allo stesso PyTupleObjectche ha ottenuto come argomento di input, se l'elemento è di tipo PySlicee la sezione restituisce l'intera tupla.

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

Ora esaminate il codice static PyObject * list_subscript(PyListObject* self, PyObject* item)e vedete di persona che qualunque sia la sezione, viene sempre restituito un nuovo oggetto elenco.


1
Si noti che questo è diverso in 2.7 , in cui una start:stopsezione sul tipo incorporato, incluso tup[:], non passa BINARY_SUBSCR. Tuttavia, la suddivisione estesa start:stop:steppassa attraverso l'abbonamento.
mercoledì

Bene, grazie aggiorneremo per specificare la versione di Python.
Fakher Mokadem il

0

Non ne sono sicuro, ma sembra che Python fornisca un nuovo puntatore allo stesso oggetto per evitare la copia poiché le tuple sono identiche (e poiché l'oggetto è una tupla, è immutabile).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.