Perché i set di Python non conservano l'ordine di inserimento?


12

Sono stato sorpreso di scoprire di recente che mentre i dadi sono garantiti per preservare l'ordine di inserimento in Python 3.7+, i set non sono:

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d
{'a': 1, 'b': 2, 'c': 3}
>>> d['d'] = 4
>>> d
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> s = {'a', 'b', 'c'}
>>> s
{'b', 'a', 'c'}
>>> s.add('d')
>>> s
{'d', 'b', 'a', 'c'}

Qual è la logica di questa differenza? Gli stessi miglioramenti dell'efficienza che hanno portato il team di Python a cambiare l'implementazione di dict non si applicano anche ai set?

Non sto cercando suggerimenti per implementazioni di set ordinati o modi per utilizzare dicts come stand-in per set. Mi chiedo solo perché il team di Python non ha fatto in modo che i set integrati preservassero l'ordine nello stesso momento in cui lo hanno fatto per i dadi.


1
Questo risponde alla tua domanda? Python ha un set ordinato?
Mihai Chelaru,

1
No, capisco che Python non ha un set ordinato integrato. Mi chiedo solo perché sia ​​così, dal momento che i dadi sono ora ordinati.
Bart Robinson,

4
I modelli di utilizzo sono diversi, quindi sono ottimizzati per diversi casi d'uso. È un'idea sbagliata comune che gli insiemi siano solo dicts con valori null in CPython, è del tutto errato: le implementazioni sono diverse. Se la tua domanda non viene chiusa, posso pubblicare una risposta dettagliata.
mercoledì

1
"I modelli di utilizzo sono diversi, quindi sono ottimizzati per diversi casi d'uso." Una buona risposta alla domanda approfondirebbe questo, credo. La domanda riguarda cosa rende i due diversi approcci ottimali per i casi d'uso corrispondenti.
Karl Knechtel,

Si noti che PyPy utilizza lo stesso ordinamento per entrambi dicte setdal 2.7.
Mister Miyagi,

Risposte:


10

Set e dadi sono ottimizzati per diversi casi d'uso. L'uso principale di un set è il test rapido dell'appartenenza, che è indipendente dall'ordine. Per i dicts, il costo della ricerca è l'operazione più critica e la chiave ha maggiori probabilità di essere presente. Con i set, la presenza o l'assenza di un elemento non è nota in anticipo, quindi l'implementazione del set deve essere ottimizzata sia per il caso trovato che per quello non trovato. Inoltre, alcune ottimizzazioni per operazioni di set comuni come unione e intersezione rendono difficile mantenere l'ordine dei set senza compromettere le prestazioni.

Mentre entrambe le strutture di dati sono basate sull'hash, è un'idea sbagliata comune che gli insiemi vengano implementati come dicts con valori null. Anche prima dell'implementazione compatta di dict in CPython 3.6, le implementazioni di set e dict differivano già in modo significativo, con un piccolo riutilizzo del codice. Ad esempio, i dicts usano il sondaggio randomizzato, ma i set usano una combinazione di sondaggio lineare e indirizzamento aperto, per migliorare la localizzazione della cache. La sonda lineare iniziale (impostazione predefinita di 9 passaggi in CPython) controllerà una serie di coppie chiave / hash adiacenti, migliorando le prestazioni riducendo il costo della gestione delle collisioni hash - l'accesso consecutivo alla memoria è più economico delle sonde sparse.

In teoria sarebbe possibile cambiare l'implementazione impostata di CPython in modo che sia simile al dict compatto, ma in pratica ci sono degli svantaggi e notevoli sviluppatori core si sono opposti a fare un tale cambiamento.

I set rimangono non ordinati. (Perché? I modelli di utilizzo sono diversi. Inoltre, un'implementazione diversa.)

- Guido van Rossum

I set utilizzano un algoritmo diverso che non è così modificabile per mantenere l'ordine di inserimento. Le operazioni set-to-set perdono flessibilità e ottimizzazioni se è necessario un ordine. La matematica degli insiemi è definita in termini di insiemi non ordinati. In breve, impostare l'ordinamento non è nell'immediato futuro.

- Raymond Hettinger

Una discussione dettagliata sull'opportunità o meno di compattare i set per 3.7 e le risposte sul perché è stato deciso contro, può essere trovata nelle mailing list di python-dev.

In sintesi, i punti principali sono che i modelli di utilizzo sono diversi (dicts di ordinamento di inserimento come ** kwargs è utile , meno per i set), i risparmi di spazio per i set di compattazione sono meno significativi (perché ci sono solo chiavi e array di hash da densify, al contrario di chiavi, hash e valori) e la suddetta ottimizzazione del probing lineare in set è incompatibile con un'implementazione compatta.

Riporterò di seguito il post di Raymond che tratta i punti più importanti.

Il 14 settembre 2016, alle 15:50, Eric Snow ha scritto:

Quindi, farò lo stesso con i set.

A meno che non abbia frainteso, Raymond si è opposto a fare un cambiamento simile per impostare.

Giusto. Ecco alcuni pensieri sull'argomento prima che le persone inizino a correre selvaggiamente.

  • Per il dict compatto, il risparmio di spazio è stato una vittoria netta con lo spazio aggiuntivo consumato dagli indici e la sovrallocazione per gli array chiave / valore / hash essendo più che compensata dalla migliore densità degli array chiave / valore / hash. Tuttavia, per gli insiemi, la rete era molto meno favorevole perché abbiamo ancora bisogno degli indici e della sovrallocazione ma possiamo compensare il costo dello spazio solo densificando solo due delle tre matrici. In altre parole, la compattazione ha più senso quando si perde spazio per chiavi, valori e hash. Se perdi una di quelle tre, smette di essere avvincente.

  • Il modello di utilizzo per i set è diverso dai dicts. Il primo ha più ricerche hit o miss. Quest'ultimo tende ad avere meno ricerche chiave mancanti. Inoltre, alcune delle ottimizzazioni per le operazioni da set a set rendono difficile mantenere l'ordine dei set senza influire sulle prestazioni.

  • Ho perseguito un percorso alternativo per migliorare le prestazioni del set. Invece di compattare (che non era gran parte dello spazio vincente e ha sostenuto il costo di un'ulteriore direzione indiretta), ho aggiunto il sondaggio lineare per ridurre il costo delle collisioni e migliorare le prestazioni della cache. Questo miglioramento è incompatibile con l'approccio di compattazione che ho sostenuto per i dizionari.

  • Per ora, l'effetto collaterale dell'ordine sui dizionari non è garantito, quindi è prematuro iniziare a insistere che anche i set vengano ordinati. I documenti si collegano già a una ricetta per la creazione di un OrderedSet ( https://code.activestate.com/recipes/576694/ ) ma sembra che l'assorbimento sia stato quasi zero. Inoltre, ora che Eric Snow ci ha dato un veloce OrderedDict, è più facile che mai costruire un OrderedSet da MutableSet e OrderedDict, ma ancora una volta non ho riscontrato alcun interesse reale perché la tipica analisi dei dati set-to-set non realmente necessità o cura dell'ordinazione. Allo stesso modo, l'uso principale dei test rapidi di appartenenza è indipendente dall'ordine.

  • Detto questo, penso che ci sia spazio per aggiungere implementazioni di set alternativi a PyPI. In particolare, ci sono alcuni casi speciali interessanti per i dati ordinabili in cui le operazioni set-to-set possono essere velocizzate confrontando intere gamme di chiavi (vedi https://code.activestate.com/recipes/230113-implementation-of- sets-using-ordinati-lists per un punto di partenza). IIRC, PyPI ha già il codice per i filtri bloom simili a quelli impostati e l'hash del cuculo.

  • Comprendo che è eccitante avere un blocco di codice maggiore accettato nel core di Python, ma ciò non dovrebbe aprirsi alle porte per impegnarsi in riscritture più importanti di altri tipi di dati a meno che non siamo sicuri che sia garantito.

- Raymond Hettinger

Da [Python-Dev] Python 3.6 dict diventa compatto e ottiene una versione privata; e le parole chiave vengono ordinate , settembre 2016.


2

discussioni

La tua domanda è germana ed è già stata ampiamente discussa sugli sviluppatori Python non molto tempo fa. R. Hettinger ha condiviso un elenco di razionali in quel thread . Lo stato della questione appare a tempo indeterminato ora, poco dopo questa risposta dettagliata di T. Peters.

In breve, l'implementazione di moderni dadi che preservano l'ordine di inserimento è unica e non è considerata appropriata con i set. In particolare, i dadi vengono usati ovunque per eseguire Python (ad es. Negli __dict__spazi dei nomi degli oggetti). Una delle principali motivazioni alla base del moderno dict era quella di ridurre le dimensioni, rendendo Python complessivamente più efficiente in termini di memoria. Al contrario, i set sono meno prevalenti dei cubetti all'interno del nucleo di Python e dissuadono quindi un tale refactoring. Vedi anche il discorso di R. Hettinger sull'implementazione moderna del dict.


prospettive

La natura non ordinata degli insiemi in Python è parallela al comportamento degli insiemi matematici . L'ordine non è garantito.

Il corrispondente concetto matematico non è ordinato e sarebbe strano imporre un ordine come quello di R. Hettinger

Se un ordine di qualsiasi tipo fosse introdotto negli insiemi in Python, questo comportamento si conformerebbe a una struttura matematica completamente separata, vale a dire un insieme ordinato (o Oset). Gli osseti svolgono un ruolo a parte in matematica, in particolare in combinatoria. Un'applicazione pratica di Oset si osserva nel cambio di campane .

Avere set non ordinati è coerente con una struttura di dati molto generica e onnipresente che sblocca la maggior parte della matematica moderna, ovvero Set Theory . Presumo, i set non ordinati in Python sono buoni da avere.

Vedi anche i post correlati che si espandono su questo argomento:

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.