Come viene implementato l'elenco di Python?


183

È un elenco collegato, un array? Ho cercato in giro e ho trovato solo persone che indovinavano. La mia conoscenza di C non è abbastanza buona per guardare il codice sorgente.

Risposte:


58

È un array dinamico . Prova pratica: l'indicizzazione prende (ovviamente con differenze estremamente piccole (0,0013 µsec!)) Allo stesso tempo, indipendentemente dall'indice:

...>python -m timeit --setup="x = [None]*1000" "x[500]"
10000000 loops, best of 3: 0.0579 usec per loop

...>python -m timeit --setup="x = [None]*1000" "x[0]"
10000000 loops, best of 3: 0.0566 usec per loop

Sarei sbalordito se IronPython o Jython usassero elenchi collegati - rovinerebbero le prestazioni di molte molte librerie ampiamente utilizzate basate sul presupposto che gli elenchi siano array dinamici.


1
@Ralf: so che la mia CPU (anche la maggior parte dell'altro hardware, del resto) è vecchia e cane lento - sul lato

88
@delnan: -1 La tua "prova pratica" è un'assurdità, così come lo sono i 6 voti. Circa il 98% del tempo viene impiegato x=[None]*1000, lasciando imprecisa la misurazione di ogni possibile differenza di accesso all'elenco. Devi separare l'inizializzazione:-s "x=[None]*100" "x[0]"
John Machin,

26
Mostra che non è un'implementazione ingenua di un elenco collegato. Non mostra definitivamente che si tratta di un array.
Michael Mior,


3
Esistono molte più strutture di una semplice lista e matrice collegate, il tempismo non è di alcuna utilità pratica per decidere tra di esse.
Ross Hemsley,

236

Il codice C è piuttosto semplice, in realtà. Espandendo una macro e potando alcuni commenti irrilevanti, si trova la struttura di base listobject.h, che definisce un elenco come:

typedef struct {
    PyObject_HEAD
    Py_ssize_t ob_size;

    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     */
    Py_ssize_t allocated;
} PyListObject;

PyObject_HEADcontiene un conteggio di riferimento e un identificatore di tipo. Quindi, è un vettore / array che si sovralloca. Il codice per ridimensionare un tale array quando è pieno è dentro listobject.c. In realtà non raddoppia l'array, ma cresce allocando

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;

alla capacità ogni volta, dov'è newsizela dimensione richiesta (non necessariamente allocated + 1perché è possibile extendtramite un numero arbitrario di elementi invece di append'ordinarli uno per uno).

Vedi anche le FAQ di Python .


6
Quindi, quando si scorre su elenchi di Python è lento quanto gli elenchi collegati, perché ogni voce è solo un puntatore, quindi ogni elemento molto probabilmente causerebbe un errore nella cache.
Kr0e

9
@ Kr0e: non se gli elementi successivi sono effettivamente lo stesso oggetto :) Ma se hai bisogno di strutture di dati più piccole / compatibili con la cache, arrayè preferibile il modulo o NumPy.
Fred Foo,

@ Kr0e Non direi che l'iterazione dell'elenco è lenta quanto gli elenchi collegati, ma che l'iterazione sui valori degli elenchi collegati è lenta come un elenco collegato, con l'avvertenza che ha citato Fred. Ad esempio, iterando su un elenco per copiarlo su un altro dovrebbe essere più veloce di un elenco collegato.
Ganea Dan Andrei,

35

In CPython, gli elenchi sono matrici di puntatori. Altre implementazioni di Python possono scegliere di memorizzarle in diversi modi.


32

Questo dipende dall'implementazione, ma IIRC:

  • CPython utilizza una matrice di puntatori
  • Jython usa un ArrayList
  • Apparentemente IronPython utilizza anche un array. Puoi sfogliare il codice sorgente per scoprirlo.

Quindi tutti hanno O (1) accesso casuale.


1
L'implementazione dipendente come in un interprete di Python che implementava elenchi come elenchi collegati sarebbe un'implementazione valida del linguaggio Python? In altre parole: O (1) l'accesso casuale agli elenchi non è garantito? Ciò non rende impossibile scrivere codice efficiente senza fare affidamento sui dettagli di implementazione?
sepp2k,

2
@sepp Credo che le liste in Python siano solo raccolte ordinate; i requisiti di implementazione e / o prestazione di detta implementazione non sono esplicitamente dichiarati
NullUserException

6
@ sppe2k: Dal momento che Python non ha una specifica standard o formale (anche se ci sono alcuni documenti che dicono "... è garantito a ..."), non puoi essere sicuro al 100% come in "questo è garantito da un pezzo di carta ". Ma poiché l' O(1)indicizzazione delle liste è un presupposto piuttosto comune e valido, nessuna implementazione oserebbe romperla.

@Paul Non dice nulla su come debba essere eseguita l'implementazione di base degli elenchi.
NullUserException,

Semplicemente non capita di specificare il grande tempo di esecuzione delle cose. La specifica della sintassi del linguaggio non significa necessariamente la stessa cosa dei dettagli di implementazione, ma spesso succede.
Paul McMillan,

26

Suggerirei l'articolo di Laurent Luce "Implementazione dell'elenco Python" . È stato davvero utile per me perché l'autore spiega come l'elenco è implementato in CPython e utilizza diagrammi eccellenti per questo scopo.

Elencare la struttura dell'oggetto C.

Un oggetto elenco in CPython è rappresentato dalla seguente struttura C. ob_itemè un elenco di puntatori agli elementi dell'elenco. allocato è il numero di slot allocati in memoria.

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

È importante notare la differenza tra gli slot allocati e le dimensioni dell'elenco. La dimensione di un elenco è la stessa di len(l). Il numero di slot allocati è ciò che è stato allocato in memoria. Spesso vedrai che l'allocazione può essere maggiore della dimensione. Questo per evitare di dover chiamare reallocogni volta che un nuovo elemento viene aggiunto all'elenco.

...

Aggiungere

Noi aggiungiamo un intero alla lista: l.append(1). Che succede?
inserisci qui la descrizione dell'immagine

Continuiamo con l'aggiunta di un elemento in più: l.append(2). list_resizeviene chiamato con n + 1 = 2 ma poiché la dimensione allocata è 4, non è necessario allocare più memoria. La stessa cosa succede quando aggiungiamo altri 2 numeri interi: l.append(3), l.append(4). Il diagramma seguente mostra ciò che abbiamo finora.

inserisci qui la descrizione dell'immagine

...

Inserire

Inseriamo un nuovo numero intero (5) in posizione 1: l.insert(1,5)e osserviamo cosa succede internamente.inserisci qui la descrizione dell'immagine

...

Pop

Quando si pop l'ultimo elemento: l.pop(), listpop()si chiama. list_resizeviene chiamato all'interno listpop()e se la nuova dimensione è inferiore alla metà della dimensione allocata, l'elenco viene ridotto.inserisci qui la descrizione dell'immagine

Puoi osservare che lo slot 4 punta ancora all'intero ma l'importante è la dimensione della lista che è ora 4. Facciamo scoppiare un altro elemento. In list_resize(), size - 1 = 4 - 1 = 3 è inferiore alla metà degli slot allocati, quindi l'elenco viene ridotto a 6 slot e la nuova dimensione dell'elenco ora è 3.

Puoi osservare che gli slot 3 e 4 puntano ancora ad alcuni numeri interi ma l'importante è la dimensione della lista che ora è 3.inserisci qui la descrizione dell'immagine

...

Cancella lista Python oggetto ha un metodo per rimuovere un elemento specifico: l.remove(5).inserisci qui la descrizione dell'immagine


Grazie, capisco la parte di collegamento dell'elenco più ora. L'elenco Python è un aggregation, non composition. Vorrei che ci fosse anche un elenco di composizione.
shuva,

22

Secondo la documentazione ,

Gli elenchi di Python sono in realtà matrici di lunghezza variabile, non elenchi collegati in stile Lisp.


5

Come altri hanno già detto in precedenza, gli elenchi (quando apprezzabilmente grandi) vengono implementati allocando una quantità fissa di spazio e, se tale spazio deve riempire, allocando una quantità maggiore di spazio e copiando gli elementi.

Per capire perché il metodo è O (1) ammortizzato, senza perdita di generalità, supponiamo di aver inserito a = 2 ^ n elementi e che ora dobbiamo raddoppiare la nostra tabella a 2 ^ (n + 1) dimensione. Ciò significa che attualmente stiamo eseguendo 2 ^ (n + 1) operazioni. Ultima copia, abbiamo eseguito 2 ^ n operazioni. Prima abbiamo fatto 2 ^ (n-1) ... fino a 8,4,2,1. Ora, se sommiamo questi, otteniamo 1 + 2 + 4 + 8 + ... + 2 ^ (n + 1) = 2 ^ (n + 2) - 1 <4 * 2 ^ n = O (2 ^ n) = O (a) inserzioni totali (ovvero O (1) tempo ammortizzato). Inoltre, va notato che se la tabella consente di eliminare la riduzione della tabella deve essere eseguita con un fattore diverso (ad es. 3x)


Per quanto ho capito, non è possibile copiare elementi più vecchi. Viene allocato più spazio, ma il nuovo spazio non è contiguo allo spazio già utilizzato e solo gli elementi più recenti da inserire vengono copiati nel nuovo spazio. Perfavore, correggimi se sbaglio.
Tushar Vazirani,

1

Un elenco in Python è qualcosa di simile a un array, in cui è possibile memorizzare più valori. L'elenco è modificabile, il che significa che è possibile modificarlo. La cosa più importante che dovresti sapere, quando creiamo un elenco, Python crea automaticamente un riferimento_id per quella variabile di elenco. Se lo si modifica assegnando altre variabili, l'elenco principale verrà modificato. Proviamo con un esempio:

list_one = [1,2,3,4]

my_list = list_one

#my_list: [1,2,3,4]

my_list.append("new")

#my_list: [1,2,3,4,'new']
#list_one: [1,2,3,4,'new']

Aggiungiamo my_listma il nostro elenco principale è cambiato. L'elenco di quella media non è stato assegnato come un elenco di copie assegnato come riferimento.


0

Nell'elenco CPython è implementato come array dinamico, e quindi quando aggiungiamo in quel momento non viene aggiunta solo una macro ma viene allocato un po 'più di spazio in modo che ogni volta non si debba aggiungere nuovo spazio.

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.