Python: defaultdict von defaultdict?


320

Gibt es eine Möglichkeit, eine zu haben defaultdict(defaultdict(int)), damit der folgende Code funktioniert?

for x in stuff:
    d[x.a][x.b] += x.c_int

dmuss ad-hoc erstellt werden, abhängig von x.aund x.bElementen.

Ich könnte benutzen:

for x in stuff:
    d[x.a,x.b] += x.c_int

aber dann könnte ich nicht verwenden:

d.keys()
d[x.a].keys()

6
Siehe ähnliche Frage Was ist der beste Weg, um verschachtelte Wörterbücher in Python zu implementieren? . Es gibt auch einige möglicherweise nützliche Informationen in Wikipedia Artikel über Autovivification .
Martineau

Antworten:


567

Ja genau so:

defaultdict(lambda: defaultdict(int))

Das Argument von a defaultdict(in diesem Fall is lambda: defaultdict(int)) wird aufgerufen, wenn Sie versuchen, auf einen nicht vorhandenen Schlüssel zuzugreifen. Der Rückgabewert wird als neuer Wert dieses Schlüssels festgelegt, was in unserem Fall bedeutet, dass der Wert von sein d[Key_doesnt_exist]wird defaultdict(int).

Wenn Sie versuchen, von diesem letzten Standarddikt auf einen Schlüssel zuzugreifen d[Key_doesnt_exist][Key_doesnt_exist], gibt er 0 zurück. Dies ist der Rückgabewert des Arguments des letzten Standarddikts, d int(). H.


7
es funktioniert super! Können Sie das Rationale hinter dieser Syntax erklären?
Jonathan

37
@Jonathan: Ja sicher, das Argument von a defaultdict(in diesem Fall ist lambda : defaultdict(int)) wird aufgerufen, wenn Sie versuchen, auf einen nicht vorhandenen Schlüssel zuzugreifen, und der Rückgabewert wird als neuer Wert dieses Schlüssels festgelegt, der in bedeutet unser Fall der Wert d[Key_dont_exist]sein wird defaultdict(int), und wenn Sie versuchen , einen Schlüssel aus diesem letzten defaultdict zugreifen dh d[Key_dont_exist][Key_dont_exist]0 zurückgegeben wird , die der Rückgabewert des Arguments des letzten ist , defaultdictdh int(), Hoffnung , das war hilfreich.
Mouad

25
Das Argument to defaultdictsollte eine Funktion sein. defaultdict(int)ist ein Wörterbuch, während lambda: defaultdict(int)eine Funktion ein Wörterbuch zurückgibt.
has2k1

27
@ has2k1 Das ist falsch. Das Argument für defaultdict muss aufrufbar sein. Ein Lambda ist ein Callable.
Niels Bom

2
@ RickyLevi, wenn Sie wollen, dass das funktioniert, können Sie einfach sagen: defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
Darophi

51

Der Parameter für den defaultdict-Konstruktor ist die Funktion, die zum Erstellen neuer Elemente aufgerufen wird. Verwenden wir also ein Lambda!

>>> from collections import defaultdict
>>> d = defaultdict(lambda : defaultdict(int))
>>> print d[0]
defaultdict(<type 'int'>, {})
>>> print d[0]["x"]
0

Seit Python 2.7 gibt es eine noch bessere Lösung mit Counter :

>>> from collections import Counter
>>> c = Counter()
>>> c["goodbye"]+=1
>>> c["and thank you"]=42
>>> c["for the fish"]-=5
>>> c
Counter({'and thank you': 42, 'goodbye': 1, 'for the fish': -5})

Einige Bonusfunktionen

>>> c.most_common()[:2]
[('and thank you', 42), ('goodbye', 1)]

Weitere Informationen finden Sie unter PyMOTW - Sammlungen - Containerdatentypen und Python-Dokumentation - Sammlungen


5
Um den Kreis hier zu schließen, möchten Sie das ursprünglich gestellte Problem d = defaultdict(lambda : Counter())eher verwenden als d = defaultdict(lambda : defaultdict(int))spezifisch ansprechen.
Kaugummi

3
@gumption Sie können d = defaultdict(Counter())in diesem Fall einfach keine Notwendigkeit für ein Lambda verwenden
Deb

3
@Deb Sie haben einen kleinen Fehler - entfernen Sie die inneren Klammern, damit Sie einen aufrufbaren anstelle eines CounterObjekts übergeben. Das heißt:d = defaultdict(Counter)
Dillon Davis

29

Ich finde es etwas eleganter zu benutzen partial:

import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)

Dies ist natürlich dasselbe wie ein Lambda.


1
Partial ist hier auch besser als Lambda, da es rekursiv angewendet werden kann :) Eine generische verschachtelte defaultdict-Factory-Methode finden Sie in meiner Antwort unten.
Campi

@Campi brauchen Sie nicht teilweise für rekursive Anwendungen, AFAICT
Clément

10

Als Referenz ist es möglich, eine generische verschachtelte defaultdictFactory-Methode zu implementieren , indem Sie:

from collections import defaultdict
from functools import partial
from itertools import repeat


def nested_defaultdict(default_factory, depth=1):
    result = partial(defaultdict, default_factory)
    for _ in repeat(None, depth - 1):
        result = partial(defaultdict, result)
    return result()

Die Tiefe definiert die Anzahl der verschachtelten Wörterbücher, bevor der in definierte Typ default_factoryverwendet wird. Zum Beispiel:

my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')

Können Sie ein Anwendungsbeispiel geben? Funktioniert nicht so, wie ich es erwartet hatte. ndd = nested_defaultdict(dict) .... ndd['a']['b']['c']['d'] = 'e'WürfeKeyError: 'b'
David Marx

Hey David, Sie müssen die Tiefe Ihres Wörterbuchs in Ihrem Beispiel 3 definieren (da Sie die default_factory auch als Wörterbuch definiert haben. Nested_defaultdict (dict, 3) wird für Sie funktionieren.
Campi

Das war super hilfreich, danke! Eine Sache, die mir aufgefallen ist, ist, dass dadurch ein default_dict bei erstellt wird depth=0, was möglicherweise nicht immer erwünscht ist, wenn die Tiefe zum Zeitpunkt des Aufrufs unbekannt ist. Einfach durch Hinzufügen einer Zeile if not depth: return default_factory()am oberen Rand der Funktion zu reparieren, obwohl es wahrscheinlich eine elegantere Lösung gibt.
Brendan

8

Frühere Antworten befassten sich mit der Erstellung von zwei oder n Ebenen defaultdict. In einigen Fällen möchten Sie eine unendliche:

def ddict():
    return defaultdict(ddict)

Verwendungszweck:

>>> d = ddict()
>>> d[1]['a'][True] = 0.5
>>> d[1]['b'] = 3
>>> import pprint; pprint.pprint(d)
defaultdict(<function ddict at 0x7fcac68bf048>,
            {1: defaultdict(<function ddict at 0x7fcac68bf048>,
                            {'a': defaultdict(<function ddict at 0x7fcac68bf048>,
                                              {True: 0.5}),
                             'b': 3})})

1
Ich liebe es. Es ist teuflisch einfach, aber unglaublich nützlich. Vielen Dank!
Rosstex

6

Andere haben Ihre Frage, wie Sie Folgendes zum Laufen bringen können, richtig beantwortet:

for x in stuff:
    d[x.a][x.b] += x.c_int

Eine Alternative wäre die Verwendung von Tupeln für Schlüssel:

d = defaultdict(int)
for x in stuff:
    d[x.a,x.b] += x.c_int
    # ^^^^^^^ tuple key

Das Schöne an diesem Ansatz ist, dass er einfach ist und leicht erweitert werden kann. Wenn Sie eine dreistufige Zuordnung benötigen, verwenden Sie einfach ein Tupel mit drei Elementen für den Schlüssel.


4
Diese Lösung bedeutet, dass es nicht einfach ist, alle d [xa] zu erhalten, da Sie jeden Schlüssel überprüfen müssen, um festzustellen, ob xa das erste Element des Tupels ist.
Matthew Schinckel

5
Wenn Sie 3 Ebenen tief verschachteln möchten, definieren Sie es einfach als 3 Ebenen: d = defaultdict (Lambda: Standarddict (Lambda: Standarddict (int)))
Matthew Schinckel
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.