Was ist der beste Weg, um verschachtelte Wörterbücher in Python zu implementieren?
Das ist eine schlechte Idee, tu es nicht. Verwenden Sie stattdessen ein reguläres Wörterbuch und verwenden Sie dict.setdefault
where apropos. Wenn also bei normaler Verwendung Schlüssel fehlen, erhalten Sie die erwarteten Ergebnisse KeyError
. Wenn Sie darauf bestehen, dieses Verhalten zu erreichen, gehen Sie wie folgt in den Fuß:
Implementieren Sie __missing__
in einer dict
Unterklasse, um eine neue Instanz festzulegen und zurückzugeben.
Dieser Ansatz ist seit Python 2.5 verfügbar (und dokumentiert) und druckt (für mich besonders wertvoll) hübsch wie ein normales Diktat anstelle des hässlichen Drucks eines autovivifizierten Standarddiktats:
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)() # retain local pointer to value
return value # faster to return than dict lookup
(Hinweis self[key]
befindet sich auf der linken Seite der Zuweisung, daher gibt es hier keine Rekursion.)
und sagen Sie, Sie haben einige Daten:
data = {('new jersey', 'mercer county', 'plumbers'): 3,
('new jersey', 'mercer county', 'programmers'): 81,
('new jersey', 'middlesex county', 'programmers'): 81,
('new jersey', 'middlesex county', 'salesmen'): 62,
('new york', 'queens county', 'plumbers'): 9,
('new york', 'queens county', 'salesmen'): 36}
Hier ist unser Verwendungscode:
vividict = Vividict()
for (state, county, occupation), number in data.items():
vividict[state][county][occupation] = number
Und nun:
>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
Kritik
Ein Kritikpunkt an diesem Containertyp ist, dass unser Code lautlos fehlschlagen kann, wenn der Benutzer einen Schlüssel falsch schreibt:
>>> vividict['new york']['queens counyt']
{}
Und außerdem hätten wir jetzt einen falsch geschriebenen Landkreis in unseren Daten:
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36},
'queens counyt': {}}}
Erläuterung:
Wir stellen nur eine weitere verschachtelte Instanz unserer Klasse Vividict
bereit, wenn auf einen Schlüssel zugegriffen wird, dieser jedoch fehlt. (Die Rückgabe der Wertzuweisung ist nützlich, da wir den Getter beim Diktat nicht zusätzlich aufrufen müssen und sie leider nicht zurückgeben können, während sie festgelegt wird.)
Beachten Sie, dass dies dieselbe Semantik wie die am besten bewertete Antwort ist, jedoch in der Hälfte der Codezeilen - die Implementierung von nosklo:
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Demonstration der Verwendung
Im Folgenden finden Sie nur ein Beispiel dafür, wie dieses Diktat leicht verwendet werden kann, um im Handumdrehen eine verschachtelte Diktatstruktur zu erstellen. Auf diese Weise können Sie schnell eine hierarchische Baumstruktur erstellen, die so tief ist, wie Sie möchten.
import pprint
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
d = Vividict()
d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)
Welche Ausgänge:
{'fizz': {'buzz': {}},
'foo': {'bar': {}, 'baz': {}},
'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}
Und wie die letzte Zeile zeigt, druckt es hübsch und zur manuellen Überprüfung. Wenn Sie Ihre Daten jedoch visuell überprüfen möchten, ist die Implementierung __missing__
, um eine neue Instanz ihrer Klasse auf den Schlüssel zu setzen und zurückzugeben, eine weitaus bessere Lösung.
Andere Alternativen zum Kontrast:
dict.setdefault
Obwohl der Fragesteller der Meinung ist, dass dies nicht sauber ist, finde ich es Vividict
mir selbst vorzuziehen .
d = {} # or dict()
for (state, county, occupation), number in data.items():
d.setdefault(state, {}).setdefault(county, {})[occupation] = number
und nun:
>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
Ein Rechtschreibfehler würde lautstark fehlschlagen und unsere Daten nicht mit schlechten Informationen überladen:
>>> d['new york']['queens counyt']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'
Außerdem denke ich, dass setdefault großartig funktioniert, wenn es in Schleifen verwendet wird und Sie nicht wissen, was Sie für Schlüssel erhalten werden, aber die wiederholte Verwendung wird ziemlich lästig, und ich glaube nicht, dass irgendjemand Folgendes beibehalten möchte:
d = dict()
d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})
Ein weiterer Kritikpunkt ist, dass setdefault eine neue Instanz erfordert, unabhängig davon, ob sie verwendet wird oder nicht. Python (oder zumindest CPython) ist jedoch ziemlich schlau im Umgang mit nicht verwendeten und nicht referenzierten neuen Instanzen. Beispielsweise wird der Speicherort im Speicher wiederverwendet:
>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)
Ein automatisch belebter Standarddikt
Dies ist eine ordentlich aussehende Implementierung, und die Verwendung in einem Skript, in dem Sie die Daten nicht überprüfen, ist genauso nützlich wie die Implementierung __missing__
:
from collections import defaultdict
def vivdict():
return defaultdict(vivdict)
Wenn Sie jedoch Ihre Daten überprüfen müssen, sehen die Ergebnisse eines automatisch belebten Standarddikts, das auf die gleiche Weise mit Daten gefüllt ist, folgendermaßen aus:
>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint;
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar':
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>,
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})
Diese Ausgabe ist ziemlich unelegant und die Ergebnisse sind ziemlich unlesbar. Die normalerweise gegebene Lösung besteht darin, zur manuellen Überprüfung rekursiv in ein Diktat umzuwandeln. Diese nicht triviale Lösung bleibt dem Leser als Übung.
Performance
Schauen wir uns zum Schluss die Leistung an. Ich subtrahiere die Kosten der Instanziierung.
>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747
Funktioniert je nach Leistung dict.setdefault
am besten. Ich würde es für Produktionscode sehr empfehlen, wenn Sie Wert auf Ausführungsgeschwindigkeit legen.
Wenn Sie dies für die interaktive Verwendung benötigen (möglicherweise in einem IPython-Notebook), spielt die Leistung keine Rolle. In diesem Fall würde ich Vividict verwenden, um die Lesbarkeit der Ausgabe zu gewährleisten. Im Vergleich zum AutoVivification-Objekt (das __getitem__
anstelle von verwendet wird __missing__
, das für diesen Zweck erstellt wurde) ist es weit überlegen.
Fazit
Die Implementierung __missing__
in einer Unterklasse dict
zum Festlegen und Zurückgeben einer neuen Instanz ist etwas schwieriger als Alternativen, bietet jedoch die Vorteile von
- einfache Instanziierung
- einfache Datenpopulation
- einfache Datenanzeige
und weil es weniger kompliziert und leistungsfähiger als das Modifizieren ist __getitem__
, sollte es diesem Verfahren vorgezogen werden.
Trotzdem hat es Nachteile:
- Schlechte Suchvorgänge schlagen stillschweigend fehl.
- Die schlechte Suche bleibt im Wörterbuch.
Daher bevorzuge ich persönlich setdefault
die anderen Lösungen und habe in jeder Situation, in der ich diese Art von Verhalten benötigt habe.
Vividict
? ZB3
undlist
für ein Diktat von Diktat von Listen, mit denen gefüllt werden könnted['primary']['secondary']['tertiary'].append(element)
. Ich könnte 3 verschiedene Klassen für jede Tiefe definieren, aber ich würde gerne eine sauberere Lösung finden.d['primary']['secondary'].setdefault('tertiary', []).append('element')
- ?? Vielen Dank für das Kompliment, aber lassen Sie mich ehrlich sein - ich benutze es nie wirklich__missing__
- ich benutze es immersetdefault
. Ich sollte wahrscheinlich meine Schlussfolgerung / mein Intro aktualisieren ...The bad lookup will remain in the dictionary.
unter dieser Lösung zu verstehen ist?. Sehr geschätzt. Thxsetdefault
wenn mehr als zwei Tiefenstufen verschachtelt würden . Es sieht so aus, als ob keine Struktur in Python eine echte Belebung bieten kann, wie beschrieben. Ich musste mich mit zwei Angabemethoden zufrieden geben, eine fürget_nested
und eine, fürset_nested
die eine Referenz für das Diktat und eine Liste verschachtelter Attribute akzeptiert wurden.Testen:
Ausgabe:
quelle
pickle
ist schrecklich zwischen Python-Versionen. Vermeiden Sie es, Daten zu speichern, die Sie behalten möchten. Verwenden Sie es nur für Caches und Dinge, die Sie nach Belieben sichern und regenerieren können. Nicht als Langzeitspeicher- oder Serialisierungsmethode.sqlite
Datenbank, um sie zu speichern.Nur weil ich noch keinen so kleinen gesehen habe, ist hier ein Diktat, das so verschachtelt wird, wie Sie möchten, kein Schweiß:
quelle
yodict = lambda: defaultdict(yodict)
.dict
. Um also vollständig gleichwertig zu sein, müssten wirx = Vdict(a=1, b=2)
arbeiten.dict
war es keine Anforderung des OP, eine Unterklasse von zu sein , die nur nach dem "besten Weg" fragte, sie zu implementieren - und außerdem nicht / nicht sowieso so viel in Python.Sie können eine YAML-Datei erstellen und mit PyYaml einlesen .
Schritt 1: Erstellen Sie eine YAML-Datei "beschäftigung.yml":
Schritt 2: Lesen Sie es in Python
und hat jetzt
my_shnazzy_dictionary
alle Ihre Werte. Wenn Sie dies im laufenden Betrieb tun müssen, können Sie die YAML als Zeichenfolge erstellen und in diese einspeisenyaml.safe_load(...)
.quelle
Da Sie ein Sternschema-Design haben, möchten Sie es möglicherweise eher wie eine relationale Tabelle und weniger wie ein Wörterbuch strukturieren.
So etwas kann einen großen Beitrag zur Erstellung eines Data Warehouse-ähnlichen Designs ohne SQL-Overhead leisten.
quelle
Wenn die Anzahl der Verschachtelungsebenen gering ist, verwende ich Folgendes
collections.defaultdict
:Mit
defaultdict
wie dies vermeidet eine Menge chaotischsetdefault()
,get()
etc.quelle
Dies ist eine Funktion, die ein verschachteltes Wörterbuch beliebiger Tiefe zurückgibt:
Verwenden Sie es so:
Durchlaufen Sie alles mit so etwas:
Dies druckt aus:
Möglicherweise möchten Sie es so gestalten, dass dem Diktat keine neuen Elemente hinzugefügt werden können. Es ist einfach, alle diese
defaultdict
s rekursiv in normaledict
s umzuwandeln .quelle
Ich finde das
setdefault
sehr nützlich; Es prüft, ob ein Schlüssel vorhanden ist, und fügt ihn hinzu, wenn nicht:setdefault
Gibt immer den relevanten Schlüssel zurück, sodass Sie die Werte von 'd
' .Wenn es um das Iterieren geht, können Sie sicher einen Generator leicht genug schreiben, wenn es in Python noch keinen gibt:
quelle
Wie andere vorgeschlagen haben, könnte eine relationale Datenbank für Sie nützlicher sein. Sie können eine speicherinterne sqlite3-Datenbank als Datenstruktur verwenden, um Tabellen zu erstellen und diese dann abzufragen.
Dies ist nur ein einfaches Beispiel. Sie können separate Tabellen für Bundesstaaten, Landkreise und Berufsbezeichnungen definieren.
quelle
collections.defaultdict
kann untergeordnet werden, um ein verschachteltes Diktat zu erstellen. Fügen Sie dieser Klasse dann alle nützlichen Iterationsmethoden hinzu.quelle
Wie für "widerliche Try / Catch-Blöcke":
ergibt
Sie können dies verwenden, um von Ihrem flachen Wörterbuchformat in ein strukturiertes Format zu konvertieren:
quelle
Sie können Addict verwenden: https://github.com/mewwts/addict
quelle
defaultdict()
ist dein Freund!Für ein zweidimensionales Wörterbuch können Sie Folgendes tun:
Für weitere Dimensionen können Sie:
quelle
Schreiben Sie einen einfachen Generator, um Ihr verschachteltes Wörterbuch einfach zu durchlaufen.
Wenn Sie also Ihr kompiliertes verschachteltes Wörterbuch haben, wird es einfach, darüber zu iterieren:
Offensichtlich kann Ihr Generator jedes Datenformat liefern, das für Sie nützlich ist.
Warum verwenden Sie try catch-Blöcke, um den Baum zu lesen? Es ist einfach genug (und wahrscheinlich sicherer), abzufragen, ob ein Schlüssel in einem Diktat vorhanden ist, bevor Sie versuchen, ihn abzurufen. Eine Funktion, die Schutzklauseln verwendet, könnte folgendermaßen aussehen:
Oder eine vielleicht etwas ausführliche Methode ist die Verwendung der get-Methode:
Für eine etwas prägnantere Art sollten Sie sich jedoch die Verwendung eines collection.defaultdict ansehen , das seit Python 2.5 Teil der Standardbibliothek ist.
Ich mache hier Annahmen über die Bedeutung Ihrer Datenstruktur, aber es sollte einfach sein, sich an das anzupassen, was Sie tatsächlich tun möchten.
quelle
Ich mag die Idee, dies in eine Klasse zu packen
__getitem__
und__setitem__
so zu implementieren, dass eine einfache Abfragesprache implementiert wird:Wenn Sie Lust haben, können Sie auch Folgendes implementieren:
aber meistens denke ich, dass es wirklich Spaß machen würde, so etwas umzusetzen: D.
quelle
Wenn Ihr Dataset nicht sehr klein bleibt, sollten Sie eine relationale Datenbank verwenden. Es macht genau das, was Sie wollen: Machen Sie es sich einfach, Zählungen hinzuzufügen, Teilmengen von Zählungen auszuwählen und sogar Zählungen nach Bundesstaat, Landkreis, Beruf oder einer beliebigen Kombination davon zu aggregieren.
quelle
Beispiel:
Bearbeiten: Gibt jetzt Wörterbücher zurück, wenn Sie mit Platzhaltern (
None
) abfragen , und ansonsten einzelne Werte.quelle
Ich habe eine ähnliche Sache vor mir. Ich habe viele Fälle, in denen ich:
Aber viele Ebenen tief gehen. Es ist das ".get (item, {})", das der Schlüssel ist, da es ein anderes Wörterbuch erstellt, wenn es noch keines gibt. In der Zwischenzeit habe ich mir überlegt, wie ich besser damit umgehen kann. Im Moment gibt es viele
Also machte ich stattdessen:
Welches hat den gleichen Effekt, wenn Sie:
Besser? Ich glaube schon.
quelle
Sie können die Rekursion in Lambdas und Standarddict verwenden, ohne Namen definieren zu müssen:
Hier ist ein Beispiel:
quelle
Ich habe diese Funktion benutzt. Es ist sicher, schnell und leicht zu warten.
Beispiel:
quelle