webentwicklung-frage-antwort-db.com.de

Wie implementiere ich verschachtelte Wörterbücher am besten?

Ich habe eine Datenstruktur, die im Wesentlichen einem verschachtelten Wörterbuch entspricht. Nehmen wir an, es sieht so aus:

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

Nun, dies aufrechtzuerhalten und zu erstellen, ist ziemlich schmerzhaft. Jedes Mal, wenn ich einen neuen Bundesstaat/Bezirk/Beruf habe, muss ich die Wörterbücher der unteren Schicht über anstößige try/catch-Blöcke erstellen. Außerdem muss ich lästige verschachtelte Iteratoren erstellen, wenn ich alle Werte durchgehen möchte. 

Ich könnte auch Tupel als Schlüssel verwenden:

{('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}

Dies macht das Iterieren der Werte sehr einfach und natürlich, aber es ist syntaktisch schmerzhafter, Dinge wie Aggregationen auszuführen und Teilmengen des Wörterbuchs zu betrachten (z. B. wenn ich nur Status für Zustand wechseln möchte).

Grundsätzlich möchte ich manchmal ein verschachteltes Wörterbuch als flaches Wörterbuch und manchmal als komplexe Hierarchie betrachten. Ich könnte das alles in eine Klasse packen, aber es scheint, als hätte jemand das schon getan. Alternativ scheint es, dass es einige wirklich elegante syntaktische Konstruktionen dafür gibt. 

Wie könnte ich das besser machen?

Nachtrag: Ich kenne setdefault(), aber es ist keine saubere Syntax. Außerdem muss für jedes von Ihnen erstellte Unterwörterbuch noch setdefault() manuell festgelegt werden.

180
YGA

Wie implementiere ich verschachtelte Wörterbücher am besten in Python?

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 (besonders für mich wertvoll) er druckt genau wie ein normales Dikt, anstelle des hässlichen Drucks eines autovivifizierten Defaultdict:

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 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 dieser Art von Container ist, dass der Code unbemerkt fehlschlagen kann, wenn der Benutzer einen Schlüssel falsch schreibt:

>>> vividict['new york']['queens counyt']
{}

Und zusätzlich 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 verschachtelte Instanz unserer Klasse Vividict bereit, wenn auf einen Schlüssel zugegriffen wird, der aber fehlt. (Die Rückgabe der Wertzuweisung ist nützlich, da wir dadurch nicht zusätzlich den Getter auf dem Diktat aufrufen müssen, und wir können ihn leider nicht zurückgeben, da er gesetzt wird.)

Beachten Sie, dass dies die gleiche Semantik wie bei der am meisten bewerteten Antwort ist, jedoch in der Hälfte der Codezeilen - der 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 Nutzung

Nachfolgend finden Sie nur ein Beispiel dafür, wie dieses Diktat leicht zum Erstellen einer verschachtelten Diktastruktur verwendet werden kann. 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 sie hübsch und für manuelle Inspektion. Wenn Sie Ihre Daten jedoch visuell prüfen möchten, ist die Implementierung von __missing__, um eine neue Instanz ihrer Klasse auf den Schlüssel zu setzen und zurückzugeben, eine weitaus bessere Lösung.

Andere Alternativen, im Gegensatz dazu:

dict.setdefault

Obwohl der Fragesteller der Meinung ist, dass dies nicht sauber ist, finde ich es der Vividict 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}}}

Eine falsche Schreibweise würde laut scheitern 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'

Darüber hinaus finde ich, dass setdefault in Schleifen hervorragend funktioniert, und Sie wissen nicht, was Sie für Schlüssel erhalten werden. Die wiederholte Verwendung wird jedoch recht mühsam, und ich glaube nicht, dass jemand Folgendes einhalten 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 benötigt, unabhängig davon, ob sie verwendet wird oder nicht. Allerdings ist Python (oder zumindest CPython) sehr geschickt im Umgang mit nicht verwendeten und nicht referenzierten neuen Instanzen. Beispielsweise verwendet es den Speicherort im Speicher:

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

Ein automatisch belegtes Defaultdict

Dies ist eine ordentlich aussehende Implementierung, und die Verwendung in einem Skript, mit dem Sie die Daten nicht untersuchen, wäre genauso nützlich wie die Implementierung von __missing__:

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

Wenn Sie Ihre Daten jedoch überprüfen müssen, sehen die Ergebnisse eines automatisch belegten Defaultdict, das 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 inelegant und die Ergebnisse sind ziemlich unlesbar. Die normalerweise gegebene Lösung besteht darin, rekursiv in ein Diktat zur manuellen Überprüfung umzuwandeln. Diese nicht triviale Lösung bleibt dem Leser als Übung überlassen.

Performance

Zum Schluss betrachten wir die Leistung. Ich subtrahiere die Kosten der Instantiierung.

>>> 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

Basierend auf der Leistung arbeitet dict.setdefault am besten. Ich würde es dringend für Produktionscode empfehlen, wenn die Ausführungsgeschwindigkeit wichtig ist. 

Wenn Sie dies für die interaktive Verwendung benötigen (in einem IPython-Notebook vielleicht), spielt die Leistung keine Rolle. In diesem Fall würde ich mich für die Lesbarkeit der Ausgabe für Vividict entscheiden. Verglichen mit dem AutoVivification-Objekt (das __getitem__ anstelle von __missing__, das zu diesem Zweck erstellt wurde) verwendet, ist es weit überlegen.

Fazit

Das Implementieren von __missing__ für eine untergeordnete dict zum Setzen und Zurückgeben einer neuen Instanz ist etwas schwieriger als Alternativen, hat aber die Vorteile

  • einfache Instantiierung
  • einfache Datenbevölkerung
  • einfache Datenanzeige

und weil es weniger kompliziert und performanter ist als __getitem__, sollte es dieser Methode vorgezogen werden.

Trotzdem hat es Nachteile:

  • Schlechte Suchvorgänge schlagen im Hintergrund fehl.
  • Die fehlerhafte Suche bleibt im Wörterbuch.

Ich persönlich bevorzuge setdefault den anderen Lösungen und habe in jeder Situation, in der ich diese Art von Verhalten benötigt habe.

149
Aaron Hall
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

Testen:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

Ausgabe:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
186
nosklo

Nur weil ich noch keinen so kleinen gesehen habe, hier ein Dikt, der so verschachtelt wird, wie Sie möchten, kein Schweiß:

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)
29
paint can

Sie können eine YAML-Datei erstellen und diese mit PyYaml einlesen.

Schritt 1: Erstellen Sie eine YAML-Datei "beschäftigung.yml":

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

Schritt 2: Lesen Sie es in Python

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

und jetzt hat my_shnazzy_dictionary alle Ihre Werte. Wenn Sie dies im laufenden Betrieb tun mussten, können Sie die YAML als Zeichenfolge erstellen und diese in yaml.safe_load(...) eingeben.

22
Pete

Da Sie ein Star-Schema-Design haben, möchten Sie es vielleicht eher wie eine relationale Tabelle und weniger wie ein Wörterbuch strukturieren.

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

Auf diese Weise kann ein Data-Warehouse-ähnlicher Entwurf ohne die SQL-Overheads wesentlich erstellt werden.

17
S.Lott

Wenn die Anzahl der Verschachtelungsebenen gering ist, verwende ich collections.defaultdict dafür:

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

Die Verwendung von defaultdict wie dieser vermeidet eine Menge chaotischer Funktionen wie setdefault(), get() usw.

13
user26294

Dies ist eine Funktion, die ein verschachteltes Wörterbuch mit beliebiger Tiefe zurückgibt:

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

Verwenden Sie es so:

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

Durchlaufen Sie alles mit etwas wie diesem:

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

Dies druckt aus:

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

Möglicherweise möchten Sie es eventuell so gestalten, dass dem Diktat keine neuen Elemente hinzugefügt werden können. Es ist einfach, all diese defaultdicts rekursiv in normale dicts umzuwandeln.

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)
9
JnBrymn

setdefault finde ich sehr nützlich; Es prüft, ob ein Schlüssel vorhanden ist, und fügt ihn hinzu, falls nicht:

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefault gibt immer den relevanten Schlüssel zurück, sodass Sie die Werte von 'd' tatsächlich aktualisieren.

Ich bin mir sicher, dass Sie beim Generieren leicht einen Generator schreiben können, wenn noch keiner in Python vorhanden ist:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)
7
andygeers

Wie andere vorgeschlagen haben, könnte eine relationale Datenbank für Sie nützlicher sein. Sie können eine im Speicher befindliche sqlite3-Datenbank als Datenstruktur verwenden, um Tabellen zu erstellen und sie anschließend abzufragen.

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

Dies ist nur ein einfaches Beispiel. Sie können separate Tabellen für Bundesstaaten, Landkreise und Berufsbezeichnungen definieren.

6

defaultdict() ist dein Freund!

Für ein zweidimensionales Wörterbuch können Sie Folgendes tun:

d = defaultdict(defaultdict)
d[1][2] = 3

Für weitere Abmessungen können Sie: 

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4
5
Paula

collections.defaultdict kann zur Erstellung einer verschachtelten Diktatur untergeordnet werden. Fügen Sie dann dieser Klasse alle nützlichen Iterationsmethoden hinzu.

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)
5
A. Coady

Wie für "abscheuliche try/catch-Blöcke":

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

erträge

{'key': {'inner key': {'inner inner key': 'value'}}}

Sie können dies verwenden, um von Ihrem flachen Wörterbuchformat in ein strukturiertes Format zu konvertieren:

fd = {('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}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v
4
vartec

Um das verschachtelte Wörterbuch zu durchlaufen, schreiben Sie einfach einen einfachen Generator.

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

Wenn Sie also ein kompiliertes verschachteltes Wörterbuch haben, wird das Durchlaufen des Wörterbuchs zum Kinderspiel:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

Offensichtlich kann Ihr Generator das für Sie nützliche Datenformat liefern.

Warum verwenden Sie try catch-Blöcke, um den Baum zu lesen? Es ist leicht genug (und wahrscheinlich sicherer), abzufragen, ob ein Schlüssel in einem Diktat vorhanden ist, bevor er versucht wird, ihn abzurufen. Eine Funktion, die Guard-Klauseln verwendet, könnte folgendermaßen aussehen:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

Oder, eine vielleicht etwas ausführlichere Methode, ist die get-Methode:

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

Etwas prägnanter wäre es jedoch, wenn Sie eine Collections.defaultdict verwenden, die seit python 2.5 zur Standardbibliothek gehört.

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

Ich mache Annahmen über die Bedeutung Ihrer Datenstruktur hier, aber es sollte leicht sein, sich an das anzupassen, was Sie tatsächlich tun möchten.

3
SpoonMeiser

Sie können Addict verwenden: https://github.com/mewwts/addict

>>> from addict import Dict
>>> my_new_shiny_dict = Dict()
>>> my_new_shiny_dict.a.b.c.d.e = 2
>>> my_new_shiny_dict
{'a': {'b': {'c': {'d': {'e': 2}}}}}
3
JnBrymn

Ich mag die Idee, dies in eine Klasse zu packen und __getitem__ und __setitem__ so zu implementieren, dass sie eine einfache Abfragesprache implementieren:

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

Wenn Sie Lust haben, können Sie auch etwas implementieren:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

aber meistens denke ich, dass so etwas wirklich Spaß machen würde: D 

2
Aaron Maenpaa

Wenn Ihr Dataset nicht zu klein bleibt, möchten Sie möglicherweise die Verwendung einer relationalen Datenbank in Betracht ziehen. Es wird genau das tun, was Sie möchten: Erleichtern Sie das Hinzufügen von Zählungen, das Auswählen von Zählmengenuntergruppen und sogar die Aggregatzählung nach Bundesstaat, Bezirk, Beruf oder einer Kombination davon.

1
allyourcode
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        Elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

Beispiel:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

Edit: Bei der Abfrage von Platzhaltern (None) werden jetzt Wörterbücher zurückgegeben.

1
Markus Jarderot

Sie können Rekursion in Lambdas und defaultdict verwenden.

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

Hier ist ein Beispiel:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})
1
topkara

Ich habe diese Funktion benutzt. es ist sicher, schnell und leicht zu warten.

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

Beispiel:

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
0
Yuda Prawira

Ich habe eine ähnliche Sache. Ich habe viele Fälle, in denen ich:

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

Aber viele Ebenen tief gehen. Es ist das ".get (item, {})", das ist der Schlüssel, da ein anderes Wörterbuch erstellt wird, wenn noch kein Wörterbuch vorhanden ist. In der Zwischenzeit habe ich überlegt, wie ich besser damit umgehen könnte. Im Moment gibt es eine Menge

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

Stattdessen habe ich gemacht:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

Was hat den gleichen Effekt, wenn Sie dies tun:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

Besser? Ich glaube schon.

0
uzi