I dizionari sono ordinati in Python 3.6+?


470

I dizionari sono ordinati in Python 3.6 (almeno con l'implementazione di CPython) a differenza delle precedenti incarnazioni. Sembra un cambiamento sostanziale, ma è solo un breve paragrafo nella documentazione . È descritto come un dettaglio dell'implementazione di CPython piuttosto che una funzionalità del linguaggio, ma implica anche che questo potrebbe diventare standard in futuro.

In che modo l'implementazione del nuovo dizionario funziona meglio di quella precedente preservando l'ordine degli elementi?

Ecco il testo della documentazione:

dict()ora utilizza una rappresentazione "compatta" introdotta da PyPy . L'utilizzo della memoria del nuovo dict () è inferiore del 20% e del 25% rispetto a Python 3.5. PEP 468 (Preservare l'ordine di ** kwargs in una funzione.) È implementato da questo. L'aspetto che preserva l'ordine di questa nuova implementazione è considerato un dettaglio dell'implementazione e non dovrebbe essere invocato (questo potrebbe cambiare in futuro, ma si desidera avere questa nuova implementazione dict nella lingua per alcune versioni prima di cambiare le specifiche della lingua imporre la semantica di conservazione dell'ordine per tutte le implementazioni attuali e future di Python; ciò aiuta anche a preservare la retrocompatibilità con le versioni precedenti del linguaggio in cui l'ordine di iterazione casuale è ancora in vigore, ad esempio Python 3.5). (Contributo di INADA Naoki innumero 27350 . Idea originariamente suggerita da Raymond Hettinger .)

Aggiornamento dicembre 2017: dictil mantenimento dell'ordine di inserzione è garantito per Python 3.7


2
Vedi questo thread sulla mailing list di Python-Dev: mail.python.org/pipermail/python-dev/2016-September/146327.html se non l'hai visto; è fondamentalmente una discussione su questi argomenti.
mgc,

1
Se ora si suppone che i kwarg siano ordinati (il che è una buona idea) e che i kwarg siano dettati, non OrderedDict, allora si potrebbe supporre che le chiavi Dict rimarranno ordinate nella futura versione di Python, nonostante la documentazione affermi diversamente.
Dmitriy Sintsov,

4
@DmitriySintsov No, non fare questa ipotesi. Questo è stato un problema sollevato durante la stesura del PEP che definisce la funzionalità di conservazione dell'ordine **kwargse come tale la formulazione utilizzata è diplomatica: **kwargsin una funzione la firma è ora garantita come una mappatura di conservazione dell'ordine di inserzione . Hanno usato il termine mappatura per non forzare altre implementazioni per ordinare il dict (e usarne uno OrderedDictinternamente) e come modo per segnalare che ciò non dovrebbe dipendere dal fatto che dictnon sia ordinato.
Dimitris Fasarakis Hilliard

7
Una buona spiegazione video di Raymond Hettinger
Alex,

1
@wazoox, l'ordinamento e la complessità dell'hashmap non sono cambiati. La modifica rende la hashmap più piccola sprecando meno spazio e lo spazio risparmiato è (di solito?) Maggiore di quello che l'array ausiliario richiede. Più veloce, più piccolo, ordinato - puoi scegliere tutti e 3
John La Rooy

Risposte:


513

I dizionari sono ordinati in Python 3.6+?

Sono ordinati per inserzione [1] . A partire da Python 3.6, per l'implementazione CPython di Python, i dizionari ricordano l'ordine degli elementi inseriti . Questo è considerato un dettaglio di implementazione in Python 3.6 ; devi usarlo OrderedDictse desideri un ordine di inserzione garantito su altre implementazioni di Python (e altri comportamenti ordinati [1] ).

A partire da Python 3.7 , questo non è più un dettaglio di implementazione e diventa invece una funzionalità linguistica. Da un messaggio python-dev di GvR :

Rendilo così. "Dict mantiene l'ordine di inserimento" è la sentenza. Grazie!

Questo significa semplicemente che puoi dipendere da esso . Altre implementazioni di Python devono anche offrire un dizionario ordinato per inserzione se desiderano essere un'implementazione conforme di Python 3.7.


In che modo l' 3.6implementazione del dizionario Python funziona meglio [2] di quella precedente preservando l'ordine degli elementi?

In sostanza, mantenendo due array .

  • Il primo array dk_entriescontiene le voci ( di tipoPyDictKeyEntry ) per il dizionario nell'ordine in cui sono state inserite. L'ordine di conservazione si ottiene essendo un array solo append in cui i nuovi elementi vengono sempre inseriti alla fine (ordine di inserimento).

  • Il secondo, dk_indicescontiene gli indici per l' dk_entriesarray (ovvero i valori che indicano la posizione della voce corrispondente in dk_entries). Questo array funge da tabella hash. Quando una chiave viene cancellata, si ottiene uno degli indici memorizzati dk_indicese la voce corrispondente viene recuperata tramite l'indicizzazione dk_entries. Poiché vengono mantenuti solo gli indici, il tipo di questo array dipende dalla dimensione complessiva del dizionario (che va dal tipo int8_t( 1byte) a int32_t/ int64_t( 4/ 8byte) su 32/ 64build di bit)

Nell'implementazione precedente, era necessario allocare una matrice sparsa di tipo PyDictKeyEntrye dimensione dk_size; sfortunatamente, ha anche provocato un sacco di spazio vuoto poiché a quell'array non è stato permesso di essere più che 2/3 * dk_sizepieno per motivi di prestazioni . (e lo spazio vuoto aveva ancoraPyDictKeyEntry dimensioni!).

Questo non è il caso ora poiché sono memorizzate solo le voci richieste (quelle che sono state inserite) e una matrice sparsa di tipo intX_t(a Xseconda della dimensione del dict) 2/3 * dk_sizeviene mantenuta piena. Lo spazio vuoto è cambiato da tipo PyDictKeyEntrya intX_t.

Quindi, ovviamente, la creazione di una matrice sparsa di tipo PyDictKeyEntryrichiede molta più memoria rispetto a una matrice sparsa per la memorizzazione di ints.

Puoi vedere la conversazione completa su Python-Dev in merito a questa funzione se ti interessa, è una buona lettura.


Nella proposta originale fatta da Raymond Hettinger , si può vedere una visualizzazione delle strutture dati utilizzate che cattura l'essenza dell'idea.

Ad esempio, il dizionario:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

è attualmente memorizzato come [keyhash, key, value]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

Invece, i dati dovrebbero essere organizzati come segue:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

Come puoi vedere ora visivamente, nella proposta originale, molto spazio è essenzialmente vuoto per ridurre le collisioni e velocizzare le ricerche. Con il nuovo approccio, riduci la memoria richiesta spostando la scarsità dove è realmente richiesta, negli indici.


[1]: dico "inserimento ordinato" e non "ordinato" poiché, con l'esistenza di OrderedDict, "ordinato" suggerisce ulteriori comportamenti che l' dictoggetto non fornisce . I OrderedDict sono reversibili, forniscono metodi sensibili all'ordine e, principalmente, forniscono test di uguaglianza sensibili all'ordine ( ==, !=). dicts attualmente non offrono nessuno di questi comportamenti / metodi.


[2]: Le nuove implementazioni del dizionario eseguono meglio la memoria in quanto progettate in modo più compatto; questo è il vantaggio principale qui. Per quanto riguarda la velocità, la differenza non è così drastica, ci sono luoghi in cui il nuovo dict potrebbe introdurre lievi regressioni ( ricerche di chiavi, ad esempio ) mentre in altri (mi viene in mente iterazione e ridimensionamento) dovrebbe essere presente un aumento delle prestazioni.

Nel complesso, le prestazioni del dizionario, soprattutto nelle situazioni di vita reale, migliorano grazie alla compattezza introdotta.


15
Quindi, cosa succede quando un oggetto viene rimosso? l' entrieselenco viene ridimensionato? o viene mantenuto uno spazio vuoto? o è compresso di volta in volta?
njzk2,

18
@ njzk2 Quando un elemento viene rimosso, l'indice corrispondente viene sostituito da DKIX_DUMMYcon un valore di -2e la voce nella entrymatrice sostituita daNULL , quando si esegue l'inserimento, i nuovi valori vengono aggiunti alla matrice delle voci, Non è stato ancora possibile discernere, ma abbastanza sicuro quando gli indici si riempiono oltre la 2/3soglia viene eseguito il ridimensionamento. Ciò può comportare una riduzione anziché una crescita se DUMMYesistono molte voci.
Dimitris Fasarakis Hilliard,

3
@Chris_Rands No, l'unica vera regressione che ho visto è sul tracker in un messaggio di Victor . A parte quel micro-segno, non ho visto nessun altro problema / messaggio che indica una grave differenza di velocità nei carichi di lavoro della vita reale. Ci sono luoghi in cui il nuovo dict potrebbe introdurre lievi regressioni (ricerche di tasti, ad esempio) mentre in altri (mi viene in mente iterazione e ridimensionamento) sarebbe presente un aumento delle prestazioni.
Dimitris Fasarakis Hilliard,

3
Correzione sulla parte di ridimensionamento : i dizionari non si ridimensionano quando si eliminano gli elementi, si ricalcolano quando si reinserisce. Quindi, se viene creato un dict d = {i:i for i in range(100)}e .poptutti gli articoli senza inserzione, le dimensioni non cambieranno. Quando lo aggiungi di nuovo, d[1] = 1viene calcolata la dimensione appropriata e il dict si ridimensiona.
Dimitris Fasarakis Hilliard,

6
@Chris_Rands Sono abbastanza sicuro che rimarrà. Il fatto è, e il motivo per cui ho cambiato la mia risposta per rimuovere le affermazioni generali su " dictessere ordinati", dictnon sono ordinati nel senso OrderedDictche lo sono. Il problema notevole è l'uguaglianza. dicthanno un ordine insensibile ==, OrderedDicthanno un ordine sensibile. Il dump OrderedDicte il passaggio dictsa confronti ora hanno confronti sensibili all'ordine potrebbe portare a molte rotture nel vecchio codice. Immagino che l'unica cosa che potrebbe cambiare su OrderedDicts sia la sua implementazione.
Dimitris Fasarakis Hilliard,

67

Di seguito è la risposta alla prima domanda originale:

Dovrei usare dicto OrderedDictin Python 3.6?

Penso che questa frase della documentazione sia effettivamente sufficiente per rispondere alla tua domanda

L'aspetto che preserva l'ordine di questa nuova implementazione è considerato un dettaglio dell'implementazione e non dovrebbe essere invocato

dictnon è esplicitamente pensato per essere una raccolta ordinata, quindi se si desidera rimanere coerenti e non fare affidamento su un effetto collaterale della nuova implementazione, è necessario attenersi OrderedDict.

Rendi il tuo codice a prova di futuro :)

C'è un dibattito al riguardo qui .

EDIT: Python 3.7 manterrà questo come una caratteristica vedere


1
Sembra che se non volessero dire che è una funzionalità reale ma solo un dettaglio di implementazione, allora non dovrebbero nemmeno metterlo nella documentazione.
xji,

3
Non sono sicuro del tuo avvertimento di modifica; poiché la garanzia si applica solo a Python 3.7, suppongo che il consiglio per Python 3.6 sia invariato, ovvero che i dicts siano ordinati in CPython ma non contino su di esso
Chris_Rands,

25

Aggiornamento: Guido van Rossum ha annunciato sulla mailing list che a partire da Python 3.7 dictin tutte le implementazioni di Python è necessario preservare l'ordine di inserimento.


2
Ora che l'ordinamento delle chiavi è lo standard ufficiale, qual è lo scopo di OrderedDict? Oppure è ora ridondante?
Jonny Waffles,

2
Immagino che OrderedDict non sarà ridondante perché ha il move_to_endmetodo e la sua uguaglianza è sensibile all'ordine: docs.python.org/3/library/… . Vedi la nota sulla risposta di Jim Fasarakis Hilliard.
fjsj,

@JonnyWaffles vedere la risposta di Jim e questo Q & A stackoverflow.com/questions/50872498/...
Chris_Rands

3
Se vuoi che il tuo codice funzioni allo stesso modo su 2.7 e 3.6 / 3.7 +, devi usare OrderedDict
boatcoder

3
Probabilmente ci sarà un "UnorderedDict" presto per la gente che amano complicarsi la loro dicts per ragioni di sicurezza; p
ZF007

9

Volevo aggiungere alla discussione sopra ma non ho la reputazione di commentare.

Python 3.8 non è ancora del tutto rilasciato, ma includerà anche la reversed()funzione sui dizionari (eliminando un'altra differenza da OrderedDict.

Dict e dictvview sono ora iterabili in ordine di inserimento inverso usando reverse (). (Contributo di Rémi Lapeyre in bpo-33462.) Scopri le novità di Python 3.8

Non vedo alcuna menzione dell'operatore di uguaglianza o altre caratteristiche, OrderedDictquindi non sono ancora del tutto uguali.

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.