C'è un modo intelligente per passare la chiave al default_factory di defaultdict?


93

Una classe ha un costruttore che accetta un parametro:

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

Da qualche parte nel codice, è utile che i valori in un dict conoscano le loro chiavi.
Voglio usare un defaultdict con la chiave passata ai valori predefiniti del neonato:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Eventuali suggerimenti?

Risposte:


127

Difficilmente si qualifica come intelligente , ma la sottoclasse è tua amica:

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)

16
Questa è esattamente la bruttezza che sto cercando di evitare ... Anche usare un semplice dict e controllare l'esistenza delle chiavi è molto più pulito.
Benjamin Nitlehoo

1
@Paul: eppure questa è la tua risposta. Bruttezza? Dai!
tzot

4
Penso che prenderò quel pezzo di codice e lo inserirò nel mio modulo di utilità generale personalizzato in modo da poterlo usare ogni volta che voglio. Non troppo brutto in quel modo ...
weronika

24
+1 Affronta direttamente la domanda dell'OP e non mi sembra "brutto". Anche una buona risposta perché molti non sembrano rendersi conto che defaultdictil __missing__()metodo di può essere sovrascritto (come può in qualsiasi sottoclasse della dictclasse incorporata dalla versione 2.5).
martineau

7
+1 L'intero scopo di __missing__ è personalizzare il comportamento per le chiavi mancanti. Anche l'approccio dict.setdefault () menzionato da @silentghost funzionerebbe (dal lato positivo, setdefault () è breve ed esiste già; dal lato negativo, soffre di problemi di efficienza ea nessuno piace davvero il nome "setdefault") .
Raymond Hettinger

26

No non c'è.

La defaultdictrealizzazione non può essere configurato per passare mancanti keyal default_factoryout-of-the-box. La tua unica opzione è implementare la tua defaultdictsottoclasse, come suggerito da @JochenRitzel, sopra.

Ma questo non è "intelligente" o quasi pulito come sarebbe una soluzione di libreria standard (se esistesse). Quindi la risposta alla tua domanda succinta, sì / no è chiaramente "No".

Peccato che alla libreria standard manchi uno strumento così spesso necessario.


Sì, sarebbe stata una scelta progettuale migliore lasciare che la fabbrica prendesse la chiave (funzione unaria piuttosto che nulla). È facile scartare un argomento quando vogliamo restituire una costante.
YvesgereY

6

Non penso che tu abbia affatto bisogno di defaultdictqui. Perché non usare solo il dict.setdefaultmetodo?

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

Questo ovviamente creerà molte istanze di C. Nel caso si tratti di un problema, penso che l'approccio più semplice farà:

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

Sarebbe più veloce di defaultdicto di qualsiasi altra alternativa per quanto posso vedere.

ETA relativo alla velocità del intest rispetto all'utilizzo della clausola try-tranne:

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

7
Ciò è altamente dispendioso nei casi in cui si accede a d molte volte e solo raramente manca una chiave: C (chiave) creerà quindi tonnellate di oggetti non necessari da raccogliere per il GC. Inoltre, nel mio caso c'è un ulteriore problema, poiché la creazione di nuovi oggetti C è lenta.
Benjamin Nitlehoo

@Paul: è vero. Suggerirei quindi un metodo ancora più semplice, vedere la mia modifica.
SilentGhost

Non sono sicuro che sia più veloce di defaultdict, ma questo è quello che faccio di solito (vedi il mio commento alla risposta di THC4k). Speravo che ci fosse un modo semplice per aggirare il fatto che default_factory non richiede argomenti, per mantenere il codice leggermente più elegante.
Benjamin Nitlehoo

5
@ SilentGhost: non capisco - come risolve il problema dell'OP? Pensavo che OP volesse qualsiasi tentativo di lettura d[key]per restituire d[key] = C(key)if key not in d. Ma la tua soluzione richiede che lui vada effettivamente e pre-impostato d[key]in anticipo? Come avrebbe fatto a sapere di cosa avrebbe avuto keybisogno?
max

2
Perché setdefault è brutto da morire e il defaultdict dalla raccolta DOVREBBE supportare una funzione di fabbrica che riceve la chiave. Che opportunità sprecata dai designer Python!
jgomo3

0

Ecco un esempio funzionante di un dizionario che aggiunge automaticamente un valore. L'attività dimostrativa nella ricerca di file duplicati in / usr / include. Nota che il dizionario di personalizzazione PathDict richiede solo quattro righe:

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)))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.