Sind Wörterbücher in Python 3.6+ bestellt?

469

Wörterbücher werden in Python 3.6 (zumindest unter der CPython-Implementierung) anders als in früheren Inkarnationen bestellt. Dies scheint eine wesentliche Änderung zu sein, ist jedoch nur ein kurzer Absatz in der Dokumentation . Es wird eher als CPython-Implementierungsdetail als als Sprachfunktion beschrieben, impliziert jedoch auch, dass dies in Zukunft zum Standard werden kann.

Wie funktioniert die neue Wörterbuchimplementierung unter Beibehaltung der Elementreihenfolge besser als die ältere?

Hier ist der Text aus der Dokumentation:

dict()Verwendet jetzt eine von PyPy entwickelte „kompakte“ Darstellung . Die Speichernutzung des neuen dict () ist im Vergleich zu Python 3.5 zwischen 20% und 25% geringer. PEP 468 (Beibehalten der Reihenfolge von ** kwargs in einer Funktion.) Wird dadurch implementiert. Der auftragserhaltende Aspekt dieser neuen Implementierung wird als Implementierungsdetail betrachtet und sollte nicht als verlässlich angesehen werden (dies kann sich in Zukunft ändern, es ist jedoch erwünscht, diese neue Dikt-Implementierung für einige Releases in der Sprache zu haben, bevor die Sprachspezifikation geändert wird Dies trägt auch dazu bei, die Abwärtskompatibilität mit älteren Versionen der Sprache zu gewährleisten, in denen die zufällige Iterationsreihenfolge noch gültig ist (z. B. Python 3.5). (Beitrag von INADA Naoki inAusgabe 27350 . Idee ursprünglich von Raymond Hettinger vorgeschlagen .)

Update Dezember 2017: Die dictBeibehaltung der Einfügereihenfolge ist für Python 3.7 garantiert

Chris_Rands
quelle
2
Siehe diesen Thread auf der Python-Dev-Mailingliste: mail.python.org/pipermail/python-dev/2016-September/146327.html, wenn Sie ihn nicht gesehen haben; Es ist im Grunde eine Diskussion um diese Themen.
mgc
1
Wenn jetzt kwargs bestellt werden sollen (was eine gute Idee ist) und kwargs diktiert werden, nicht OrderedDict, dann könnte man davon ausgehen, dass diktschlüssel in der zukünftigen Version von Python bestellt bleiben, obwohl in der Dokumentation etwas anderes angegeben ist.
Dmitriy Sintsov
4
@DmitriySintsov Nein, machen Sie diese Annahme nicht. Dies war ein Problem, das während des Schreibens des PEP angesprochen wurde und das Merkmal der Ordnungserhaltung definiert, **kwargsund als solches ist der verwendete Wortlaut diplomatisch: **kwargsIn einer Funktionssignatur ist jetzt garantiert, dass es sich um eine Zuordnung handelt, die die Ordnungserhaltung einfügt . Sie haben den Begriff Mapping verwendet, um keine anderen Implementierungen zu zwingen, das Diktat zu ordnen (und ein OrderedDictinternes zu verwenden) und um zu signalisieren, dass dies nicht von der Tatsache abhängen soll, dass das dictDiktat nicht geordnet ist.
Dimitris Fasarakis Hilliard
7
Eine gute Videoerklärung von Raymond Hettinger
Alex
1
@wazoox, die Reihenfolge und Komplexität der Hashmap hat sich nicht geändert. Durch die Änderung wird die Hashmap kleiner, indem weniger Speicherplatz verschwendet wird, und der gespeicherte Speicherplatz ist (normalerweise?) Mehr als das Hilfsarray benötigt. Schneller, kleiner, bestellt - Sie können alle 3 auswählen.
John La Rooy

Antworten:

512

Sind Wörterbücher in Python 3.6+ bestellt?

Sie sind Einfügungsreihenfolge [1] . Ab Python 3.6 merken sich Wörterbücher für die CPython-Implementierung von Python die Reihenfolge der eingefügten Elemente . Dies wird in Python 3.6 als Implementierungsdetail betrachtet . Sie müssen verwenden, OrderedDictwenn Sie eine Einfügereihenfolge wünschen, die für andere Implementierungen von Python (und anderes geordnetes Verhalten [1] ) garantiert ist .

Ab Python 3.7 ist dies kein Implementierungsdetail mehr, sondern wird zu einer Sprachfunktion. Aus einer Python-Dev-Nachricht von GvR :

Mach es so. "Dikt hält Einfügereihenfolge" ist das Urteil. Vielen Dank!

Dies bedeutet einfach, dass Sie sich darauf verlassen können . Andere Implementierungen von Python müssen ebenfalls ein Wörterbuch mit Einfügungsreihenfolge anbieten, wenn sie eine konforme Implementierung von Python 3.7 sein sollen.


Wie funktioniert die Python- 3.6Wörterbuchimplementierung besser [2] als die ältere, während die Elementreihenfolge beibehalten wird?

Im Wesentlichen durch Beibehalten von zwei Arrays .

  • Das erste Array dk_entriesenthält die Einträge ( vom TypPyDictKeyEntry ) für das Wörterbuch in der Reihenfolge, in der sie eingefügt wurden. Die Beibehaltung der Reihenfolge wird dadurch erreicht, dass dies ein Array nur zum Anhängen ist, in das immer neue Elemente am Ende eingefügt werden (Einfügereihenfolge).

  • Die zweite dk_indicesenthält die Indizes für das dk_entriesArray (dh Werte, die die Position des entsprechenden Eintrags in angeben dk_entries). Dieses Array fungiert als Hash-Tabelle. Wenn ein Schlüssel gehasht wird, führt dies zu einem der darin gespeicherten Indizes, dk_indicesund der entsprechende Eintrag wird durch Indizierung abgerufen dk_entries. Da nur Indizes beibehalten werden, hängt der Typ dieses Arrays von der Gesamtgröße des Wörterbuchs ab (von Typ int8_t( 1Byte) bis int32_t/ int64_t( 4/ 8Byte) bei 32/ 64Bit-Builds).

In der vorherigen Implementierung musste ein spärliches Array von Typ PyDictKeyEntryund Größe dk_sizezugewiesen werden. Leider führte dies auch zu viel leerem Speicherplatz, da dieses Array aus Leistungsgründen nicht mehr als 2/3 * dk_sizevoll sein durfte . (und der leere Raum noch hatte eine PyDictKeyEntryGröße!).

Dies ist jetzt nicht der Fall, da nur die erforderlichen Einträge gespeichert werden (die eingefügt wurden) und ein spärliches Array vom Typ intX_t( Xabhängig von der Größe des Diktats) 2/3 * dk_sizevoll bleibt. Der leere Raum wurde von Typ PyDictKeyEntryzu geändert intX_t.

Das Erstellen eines spärlichen Arrays vom Typ PyDictKeyEntryist daher viel speicherintensiver als ein spärliches Array zum Speichern von ints.

Sie können die vollständige Konversation auf Python-Dev über diese Funktion sehen, wenn Sie interessiert sind, es ist eine gute Lektüre.


In dem ursprünglichen Vorschlag von Raymond Hettinger ist eine Visualisierung der verwendeten Datenstrukturen zu sehen, die den Kern der Idee erfasst.

Zum Beispiel das Wörterbuch:

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

wird derzeit als [keyhash, key, value] gespeichert:

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

Stattdessen sollten die Daten wie folgt organisiert sein:

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

Wie Sie jetzt visuell sehen können, ist im ursprünglichen Vorschlag im Wesentlichen viel Platz leer, um Kollisionen zu reduzieren und das Nachschlagen zu beschleunigen. Mit dem neuen Ansatz reduzieren Sie den erforderlichen Speicher, indem Sie die Spärlichkeit in den Indizes dorthin verschieben, wo sie wirklich benötigt wird.


[1]: Ich sage "Einfügung bestellt" und nicht "bestellt", da "bestellt" mit der Existenz von OrderedDict weiteres Verhalten nahe legt, das das dictObjekt nicht bereitstellt . OrderedDicts sind reversibel, bieten auftragssensitive Methoden und bieten hauptsächlich auftragssensitive Gleichheitstests ( ==, !=). dicts bieten derzeit keine dieser Verhaltensweisen / Methoden an.


[2]: Die neuen Wörterbuchimplementierungen bieten eine bessere Speicherleistung, da sie kompakter gestaltet sind. Das ist hier der Hauptvorteil. In Bezug auf die Geschwindigkeit ist der Unterschied nicht so drastisch. Es gibt Stellen, an denen das neue Diktat leichte Regressionen einführen kann ( z. B. Key-Lookups ), während in anderen Fällen (Iteration und Größenänderung) ein Leistungsschub vorhanden sein sollte.

Insgesamt verbessert sich die Leistung des Wörterbuchs, insbesondere in realen Situationen, aufgrund der eingeführten Kompaktheit.

Dimitris Fasarakis Hilliard
quelle
15
Was passiert also, wenn ein Gegenstand entfernt wird? entriesWird die Größe der Liste geändert? oder wird ein Leerzeichen gehalten? oder wird es von Zeit zu Zeit komprimiert?
NJZK2
18
@ njzk2 Wenn ein Element entfernt wird, wird der entsprechende Index durch den DKIX_DUMMYWert von ersetzt -2und der Eintrag im entryArray durch ersetztNULL . Wenn das Einfügen durchgeführt wird, werden die neuen Werte an das Eintragsarray angehängt. Es konnte noch nicht erkannt werden. aber ziemlich sicher, wenn die Indizes über den 2/3Schwellenwert hinaus gefüllt werden, wird eine Größenänderung durchgeführt. Dies kann dazu führen, dass viele DUMMYEinträge verkleinert werden, anstatt zu wachsen .
Dimitris Fasarakis Hilliard
3
@Chris_Rands Nein, die einzige tatsächliche Regression, die ich gesehen habe, ist auf dem Tracker in einer Nachricht von Victor . Abgesehen von diesem Mikrobenchmark habe ich kein anderes Problem / keine andere Meldung gesehen, die auf einen gravierenden Geschwindigkeitsunterschied bei der tatsächlichen Arbeitsbelastung hinweist. Es gibt Stellen, an denen das neue Diktat leichte Regressionen einführen könnte (z. B. Key-Lookups), während an anderen Stellen (Iteration und Größenänderung in den Sinn kommen) eine Leistungssteigerung vorhanden wäre.
Dimitris Fasarakis Hilliard
3
Korrektur des Größenänderungsteils : Wörterbücher werden beim Löschen von Elementen nicht in der Größe geändert, sondern beim erneuten Einfügen neu berechnet. Wenn also ein Diktat mit erstellt wird d = {i:i for i in range(100)}und Sie .popalle Elemente ohne Einfügen verwenden, ändert sich die Größe nicht. Wenn Sie es erneut hinzufügen, d[1] = 1wird die entsprechende Größe berechnet und die Größe des Diktats geändert.
Dimitris Fasarakis Hilliard
6
@ Chris_Rands Ich bin mir ziemlich sicher, dass es bleibt. Die Sache ist, und der Grund, warum ich meine Antwort geändert habe, um pauschale Aussagen über " dictbestellt werden" zu entfernen , dictist nicht in dem Sinne geordnet, wie OrderedDictes ist. Das bemerkenswerte Problem ist die Gleichstellung. dicts haben auftragsunempfindlich ==, OrderedDicts haben auftragsempfindliche. Das Dumping von OrderedDicts und das Ändern dictsauf jetzt auftragsabhängige Vergleiche können zu einem erheblichen Bruch des alten Codes führen. Ich vermute, das einzige, was sich an OrderedDicts ändern könnte, ist die Implementierung.
Dimitris Fasarakis Hilliard
67

Unten wird die ursprüngliche erste Frage beantwortet:

Soll ich dictoder OrderedDictin Python 3.6 verwenden?

Ich denke, dieser Satz aus der Dokumentation reicht tatsächlich aus, um Ihre Frage zu beantworten

Der auftragserhaltende Aspekt dieser neuen Implementierung wird als Implementierungsdetail betrachtet und sollte nicht als verlässlich angesehen werden

dictist nicht explizit als geordnete Sammlung gedacht. Wenn Sie also konsistent bleiben und sich nicht auf einen Nebeneffekt der neuen Implementierung verlassen möchten, sollten Sie dabei bleiben OrderedDict.

Machen Sie Ihren Code zukunftssicher :)

Es gibt eine Debatte darüber , dass hier .

EDIT: Python 3.7 hält dies als eine Funktion siehe

Maresh
quelle
1
Es scheint, dass sie es dann nicht einmal in die Dokumentation aufnehmen sollten, wenn sie nicht meinten, dass es sich um eine echte Funktion handelt, sondern nur um ein Implementierungsdetail.
Xji
3
Ich bin mir nicht sicher, ob Sie eine Einschränkung beim Bearbeiten haben. Da die Garantie nur für Python 3.7 gilt, gehe ich davon aus, dass der Rat für Python 3.6 unverändert bleibt, dh Diktate werden in CPython bestellt, aber nicht damit
gerechnet
25

Update: Guido van Rossum kündigte auf der Mailingliste an, dass ab Python 3.7 dictin allen Python-Implementierungen die Einfügereihenfolge beibehalten werden muss.

fjsj
quelle
2
Nun, da die Schlüsselbestellung der offizielle Standard ist, was ist der Zweck des OrderedDict? Oder ist es jetzt überflüssig?
Jonny Waffles
2
Ich denke, OrderedDict wird nicht redundant sein, da es die move_to_endMethode hat und seine Gleichheit auftragsabhängig ist: docs.python.org/3/library/… . Siehe den Hinweis zu Jim Fasarakis Hilliards Antwort.
FJSJ
@JonnyWaffles siehe Jims Antwort und diese Fragen und Antworten stackoverflow.com/questions/50872498/…
Chris_Rands
3
Wenn Sie möchten, dass Ihr Code auf 2.7 und 3.6 / 3.7 + gleich ausgeführt wird, müssen Sie OrderedDict
Boatcoder
3
Wahrscheinlich wird es bald ein "UnorderedDict" für Leute geben, die ihre Diktate aus Sicherheitsgründen
gerne belästigen
9

Ich wollte die obige Diskussion ergänzen, habe aber nicht den Ruf, einen Kommentar abzugeben.

Python 3.8 ist noch nicht ganz veröffentlicht, wird aber sogar die reversed()Funktion für Wörterbücher enthalten (wodurch ein weiterer Unterschied beseitigt wird) OrderedDict.

Dict und Dictviews können jetzt in umgekehrter Einfügereihenfolge mit reverse () iteriert werden. (Beitrag von Rémi Lapeyre in bpo-33462.) Sehen Sie, was in Python 3.8 neu ist

Ich sehe keine Erwähnung des Gleichheitsoperators oder anderer Merkmale von, OrderedDictso dass sie immer noch nicht ganz gleich sind.

rkengler
quelle