Gibt es eine clevere Möglichkeit, den Schlüssel an die default_factory von defaultdict zu übergeben?

91

Eine Klasse hat einen Konstruktor, der einen Parameter akzeptiert:

class C(object):
    def __init__(self, v):
        self.v = v
        ...

Irgendwo im Code ist es nützlich, dass Werte in einem Diktat ihre Schlüssel kennen.
Ich möchte ein Standarddiktat mit dem Schlüssel verwenden, der an neugeborene Standardwerte übergeben wird:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Irgendwelche Vorschläge?

Benjamin Nitlehoo
quelle

Antworten:

126

Es qualifiziert sich kaum als klug - aber Unterklassen sind dein Freund:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)
Jochen Ritzel
quelle
16
Das ist genau die Hässlichkeit, die ich zu vermeiden versuche ... Selbst wenn man ein einfaches Diktat verwendet und nach Schlüsselexistenz sucht, ist das viel sauberer.
Benjamin Nitlehoo
@ Paul: und doch ist dies deine Antwort. Hässlichkeit? Komm schon!
Zot
4
Ich denke, ich nehme einfach diesen Code und füge ihn in mein personalisiertes Modul für allgemeine Dienstprogramme ein, damit ich ihn verwenden kann, wann immer ich will. Nicht zu hässlich auf diese Weise ...
weronika
24
+1 Adressiert direkt die Frage des OP und sieht für mich nicht "hässlich" aus. Auch eine gute Antwort, da viele nicht zu erkennen scheinen, dass defaultdictdie __missing__()Methode überschrieben werden kann (wie in jeder Unterklasse der integrierten dictKlasse seit Version 2.5).
Martineau
7
+1 Der gesamte Zweck von __missing__ besteht darin, das Verhalten für fehlende Schlüssel anzupassen. Der von @silentghost erwähnte Ansatz dict.setdefault () würde ebenfalls funktionieren (auf der positiven Seite ist setdefault () kurz und existiert bereits; auf der negativen Seite leidet er unter Effizienzproblemen und niemand mag den Namen "setdefault" wirklich). .
Raymond Hettinger
26

Nein, da ist kein.

Die defaultdictImplementierung kann nicht so konfiguriert werden, dass sie fehlend keyan den default_factoryStandard übergeben wird. Sie können nur Ihre eigene defaultdictUnterklasse implementieren , wie oben von @JochenRitzel vorgeschlagen.

Aber das ist nicht "klug" oder fast so sauber wie eine Standardbibliothekslösung (falls vorhanden). Somit lautet die Antwort auf Ihre prägnante Ja / Nein-Frage eindeutig "Nein".

Es ist schade, dass in der Standardbibliothek ein so häufig benötigtes Tool fehlt.

Stuart Berg
quelle
Ja, es wäre eine bessere Wahl gewesen, wenn die Fabrik den Schlüssel übernehmen würde (unäre Funktion statt null). Es ist einfach, ein Argument zu verwerfen, wenn wir eine Konstante zurückgeben möchten.
YvesgereY
6

Ich glaube nicht, dass du defaultdicthier überhaupt brauchst . Warum nicht einfach die dict.setdefaultMethode anwenden?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

Das würde natürlich viele Instanzen von schaffen C. Falls es sich um ein Problem handelt, reicht meiner Meinung nach der einfachere Ansatz aus:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

defaultdictSoweit ich sehen kann, wäre es schneller als die oder eine andere Alternative.

ETA in Bezug auf die inTestgeschwindigkeit im Vergleich zur Verwendung der Try-Except-Klausel:

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264
SilentGhost
quelle
7
Dies ist in Fällen, in denen auf d häufig zugegriffen wird und nur selten ein Schlüssel fehlt, äußerst verschwenderisch: C (Schlüssel) erzeugt somit Tonnen nicht benötigter Objekte, die der GC sammeln kann. In meinem Fall gibt es auch zusätzliche Schmerzen, da das Erstellen neuer C-Objekte langsam ist.
Benjamin Nitlehoo
@ Paul: das stimmt. Ich würde dann noch einfachere Methode vorschlagen, siehe meine Bearbeitung.
SilentGhost
Ich bin mir nicht sicher, ob es schneller als defaultdict ist, aber das mache ich normalerweise (siehe meinen Kommentar zur Antwort von THC4k). Ich hoffte, dass es eine einfache Möglichkeit gibt, die Tatsache zu umgehen, dass default_factory keine Argumente benötigt, um den Code etwas eleganter zu halten.
Benjamin Nitlehoo
5
@ SilentGhost: Ich verstehe nicht - wie löst dies das Problem des OP? Ich dachte, OP wollte jeden Versuch zu lesen, d[key]um zurückzukehren, d[key] = C(key)wenn key not in d. Aber Ihre Lösung erfordert, dass er tatsächlich d[key]im Voraus voreingestellt wird ? Wie würde er wissen, was keyer brauchen würde?
Max
2
Weil setdefault höllisch hässlich ist und das Standarddiktat aus der Sammlung eine Factory-Funktion unterstützen SOLLTE, die den Schlüssel erhält. Was für eine vergebliche Gelegenheit von den Python-Designern!
jgomo3
0

Hier ist ein funktionierendes Beispiel für ein Wörterbuch, das automatisch einen Wert hinzufügt. Die Demonstrationsaufgabe beim Suchen doppelter Dateien in / usr / include. Beachten Sie, dass das benutzerdefinierte Wörterbuch PathDict nur vier Zeilen benötigt:

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
gerardw
quelle