Come funziona collections.defaultdict?


532

Ho letto gli esempi nei documenti di Python, ma non riesco ancora a capire cosa significhi questo metodo. Qualcuno può aiutare? Ecco due esempi dai documenti di Python

>>> from collections import defaultdict

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]

e

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

i parametri inte listsono per cosa?


15
A proposito, a seconda del caso d'uso, non dimenticare di bloccare il valore predefinito per l'utilizzo in sola lettura impostandone una default_factory = Nonevolta terminato il popolamento del valore predefinito. Vedere questa domanda .
Acumenus,

Risposte:


598

Di solito, un dizionario Python genera un KeyErrorse si tenta di ottenere un elemento con una chiave che non è attualmente nel dizionario. Il defaultdictcontrario creerà semplicemente tutti gli elementi a cui si tenta di accedere (purché ovviamente non esistano ancora). Per creare un tale elemento "predefinito", chiama l'oggetto funzione che passi al costruttore (più precisamente, è un oggetto "richiamabile" arbitrario, che include oggetti funzione e tipo). Per il primo esempio, gli elementi predefiniti vengono creati utilizzando int(), che restituirà l'oggetto intero 0. Per il secondo esempio, gli elementi predefiniti vengono creati utilizzando list(), che restituisce un nuovo oggetto elenco vuoto.


4
Funzionalmente è diverso dall'uso di d.get (key, default_val)?
Ambareesh

29
@Ambareesh d.get(key, default)non modificherà mai il tuo dizionario, restituirà solo il valore predefinito e lascerà invariato il dizionario. defaultdictd'altra parte, inserirà una chiave nel dizionario se non è ancora presente. Questa è una grande differenza; vedere gli esempi nella domanda per capire il perché.
Sven Marnach,

Come facciamo a sapere qual è il valore predefinito per ciascun tipo? 0 per int () e [] per list () sono intuitivi, ma possono esserci anche tipi più complessi o auto-definiti.
Sean

1
@Sean defaultdictchiama qualunque costruttore tu passi. Se passi un tipo T, i valori verranno costruiti usando T(). Non tutti i tipi possono essere costruiti senza passare alcun parametro. Se vuoi costruire un tale tipo, hai bisogno di una funzione wrapper o qualcosa del genere functools.partial(T, arg1, arg2).
Sven Marnach,

224

defaultdictsignifica che se una chiave non viene trovata nel dizionario, invece di KeyErroressere lanciata, viene creata una nuova voce. Il tipo di questa nuova voce è dato dall'argomento di defaultdict.

Per esempio:

somedict = {}
print(somedict[3]) # KeyError

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0

10
"Il tipo di questa nuova coppia è dato dall'argomento di defaultdict." Nota che l'argomento può essere qualsiasi oggetto richiamabile, non solo digitare funzioni. Ad esempio, se foo era una funzione che restituiva "bar", foo poteva essere usato come argomento di default dict e se si accedeva a una chiave non presente, il suo valore sarebbe impostato su "bar".
lf215,

13
O se vuoi solo restituire "bar": somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert,

La quarta riga ha restituito 0il numero intero, se era someddict = defaultdict(list)restituito [ ]. 0 è il numero intero predefinito? Oppure [] l'elenco predefinito?
Gathide,

Nessuno dei due. 0è immutabile - in CPython tutti i valori da -5a 256sono singleton memorizzati nella cache ma si tratta di un comportamento specifico dell'implementazione - in entrambi i casi una nuova istanza viene "creata" ogni volta con int()o list(). In questo modo, d[k].append(v)può funzionare senza riempire il dizionario con riferimenti allo stesso elenco, il che renderebbe defaultdictquasi inutile. Se questo fosse il comportamento, defaultdictprenderebbe un valore, non un lambda, come parametro. (
Ci

93

defaultdict

"Il dizionario standard include il metodo setdefault () per recuperare un valore e stabilire un valore predefinito se il valore non esiste. Al contrario, defaultdictconsente al chiamante di specificare il valore predefinito (valore da restituire) in anticipo quando il contenitore viene inizializzato."

come definito da Doug Hellmann in The Python Standard Library con l'esempio

Come usare defaultdict

Importa defaultdict

>>> from collections import defaultdict

Inizializza defaultdict

Inizializzalo passando

richiamabile come primo argomento (obbligatorio)

>>> d_int = defaultdict(int)
>>> d_list = defaultdict(list)
>>> def foo():
...     return 'default value'
... 
>>> d_foo = defaultdict(foo)
>>> d_int
defaultdict(<type 'int'>, {})
>>> d_list
defaultdict(<type 'list'>, {})
>>> d_foo
defaultdict(<function foo at 0x7f34a0a69578>, {})

** kwargs come secondo argomento (facoltativo)

>>> d_int = defaultdict(int, a=10, b=12, c=13)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

o

>>> kwargs = {'a':10,'b':12,'c':13}
>>> d_int = defaultdict(int, **kwargs)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

Come funziona

Come è una classe figlio del dizionario standard, può svolgere tutte le stesse funzioni.

Ma in caso di passaggio di una chiave sconosciuta restituisce il valore predefinito invece di errore. Ad esempio:

>>> d_int['a']
10
>>> d_int['d']
0
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12, 'd': 0})

Nel caso in cui si desideri modificare il valore predefinito, sovrascrivere default_factory:

>>> d_int.default_factory = lambda: 1
>>> d_int['e']
1
>>> d_int
defaultdict(<function <lambda> at 0x7f34a0a91578>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0})

o

>>> def foo():
...     return 2
>>> d_int.default_factory = foo
>>> d_int['f']
2
>>> d_int
defaultdict(<function foo at 0x7f34a0a0a140>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0, 'f': 2})

Esempi nella domanda

Esempio 1

Poiché int è stato passato come default_factory, qualsiasi chiave sconosciuta restituirà 0 per impostazione predefinita.

Ora che la stringa viene passata nel ciclo, aumenterà il conteggio di quegli alfabeti in d.

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> d.default_factory
<type 'int'>
>>> for k in s:
...     d[k] += 1
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]
>>> d
defaultdict(<type 'int'>, {'i': 4, 'p': 2, 's': 4, 'm': 1})

Esempio 2

Poiché un elenco è stato passato come default_factory, qualsiasi chiave sconosciuta (inesistente) restituirà [] (cioè elenco) per impostazione predefinita.

Ora che l'elenco delle tuple viene passato nel ciclo, aggiungerà il valore in d [colore]

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> d.default_factory
<type 'list'>
>>> for k, v in s:
...     d[k].append(v)
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
>>> d
defaultdict(<type 'list'>, {'blue': [2, 4], 'red': [1], 'yellow': [1, 3]})

20

I dizionari sono un modo conveniente per memorizzare i dati per il successivo recupero per nome (chiave). Le chiavi devono essere oggetti unici, immutabili e in genere stringhe. I valori in un dizionario possono essere qualsiasi cosa. Per molte applicazioni, i valori sono tipi semplici come numeri interi e stringhe.

Diventa più interessante quando i valori in un dizionario sono raccolte (elenchi, dicts, ecc.) In questo caso, il valore (un elenco vuoto o un dict) deve essere inizializzato la prima volta che viene utilizzata una determinata chiave. Sebbene ciò sia relativamente facile da eseguire manualmente, il tipo defaultdict automatizza e semplifica questo tipo di operazioni. Un defaultdict funziona esattamente come un normale dict, ma è inizializzato con una funzione ("factory predefinita") che non accetta argomenti e fornisce il valore predefinito per una chiave inesistente.

Un defaultdict non genererà mai un KeyError. Qualsiasi chiave che non esiste ottiene il valore restituito dalla fabbrica predefinita.

from collections import defaultdict
ice_cream = defaultdict(lambda: 'Vanilla')

ice_cream['Sarah'] = 'Chunky Monkey'
ice_cream['Abdul'] = 'Butter Pecan'

print(ice_cream['Sarah'])
>>>Chunky Monkey

print(ice_cream['Joe'])
>>>Vanilla

Ecco un altro esempio su Come usando defaultdict, possiamo ridurre la complessità

from collections import defaultdict
# Time complexity O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Time Complexity O(n), using hash tables.
def delete_nth(array,n):
    result = []
    counts = defaultdict(int)

    for i in array:
        if counts[i] < n:
            result.append(i)
            counts[i] += 1
    return result


x = [1,2,3,1,2,1,2,3]
print(delete_nth(x, n=2))
print(delete_nth_naive(x, n=2))

In conclusione, ogni volta che è necessario un dizionario e il valore di ciascun elemento deve iniziare con un valore predefinito, utilizzare un valore predefinito.


18

C'è una grande spiegazione dei defaultdict qui: http://ludovf.net/blog/python-collections-defaultdict/

Fondamentalmente, i parametri int e list sono funzioni che si passano. Ricorda che Python accetta i nomi delle funzioni come argomenti. int restituisce 0 per impostazione predefinita e list restituisce un elenco vuoto quando viene chiamato tra parentesi.

Nei dizionari normali, se nel tuo esempio provo a chiamare d[a], visualizzerò un errore (KeyError), poiché esistono solo le chiavi m, s, i e p e la chiave a non è stata inizializzata. Ma in un defaultdict, prende un nome di funzione come argomento, quando si tenta di utilizzare una chiave che non è stata inizializzata, chiama semplicemente la funzione che è stata passata e assegna il valore restituito come valore della nuova chiave.


7

Dal momento che la domanda riguarda "come funziona", alcuni lettori potrebbero voler vedere più dettagli. In particolare, il metodo in questione è il __missing__(key)metodo. Vedi: https://docs.python.org/2/library/collections.html#defaultdict-objects .

Più concretamente, questa risposta mostra come utilizzare __missing__(key)in modo pratico: https://stackoverflow.com/a/17956989/1593924

Per chiarire cosa significa "richiamabile", ecco una sessione interattiva (dalla 2.7.6 ma dovrebbe funzionare anche in v3):

>>> x = int
>>> x
<type 'int'>
>>> y = int(5)
>>> y
5
>>> z = x(5)
>>> z
5

>>> from collections import defaultdict
>>> dd = defaultdict(int)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd = defaultdict(x)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd['a']
0
>>> dd
defaultdict(<type 'int'>, {'a': 0})

Quello era l'uso più tipico di defaultdict (ad eccezione dell'uso inutile della variabile x). Puoi fare la stessa cosa con 0 del valore predefinito esplicito, ma non con un valore semplice:

>>> dd2 = defaultdict(0)

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    dd2 = defaultdict(0)
TypeError: first argument must be callable

Invece, il seguente funziona perché passa in una funzione semplice (crea al volo una funzione senza nome che non accetta argomenti e restituisce sempre 0):

>>> dd2 = defaultdict(lambda: 0)
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {})
>>> dd2['a']
0
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {'a': 0})
>>> 

E con un valore predefinito diverso:

>>> dd3 = defaultdict(lambda: 1)
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {})
>>> dd3['a']
1
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
>>> 

7

Il mio 2 ¢: è anche possibile eseguire la sottoclasse di defaultdict:

class MyDict(defaultdict):
    def __missing__(self, key):
        value = [None, None]
        self[key] = value
        return value

Questo potrebbe tornare utile per casi molto complessi.


4

Il comportamento di defaultdictpuò essere facilmente imitato usando dict.setdefaultanziché d[key]in ogni chiamata.

In altre parole, il codice:

from collections import defaultdict

d = defaultdict(list)

print(d['key'])                        # empty list []
d['key'].append(1)                     # adding constant 1 to the list
print(d['key'])                        # list containing the constant [1]

è equivalente a:

d = dict()

print(d.setdefault('key', list()))     # empty list []
d.setdefault('key', list()).append(1)  # adding constant 1 to the list
print(d.setdefault('key', list()))     # list containing the constant [1]

L'unica differenza è che, usando defaultdict, il costruttore della lista viene chiamato solo una volta, e usando dict.setdefaultil costruttore della lista viene chiamato più spesso (ma il codice può essere riscritto per evitarlo, se necessario).

Alcuni potrebbero obiettare che c'è una considerazione delle prestazioni, ma questo argomento è un campo minato. Questo post mostra che non c'è un grande miglioramento delle prestazioni nell'uso di defaultdict, per esempio.

IMO, defaultdict è una raccolta che aggiunge più confusione che vantaggi al codice. Inutile per me, ma altri potrebbero pensare diversamente.


3

Lo strumento defaultdict è un contenitore nella classe di collezioni di Python. È simile al normale contenitore del dizionario (dict), ma presenta una differenza: il tipo di dati dei campi valore viene specificato al momento dell'inizializzazione.

Per esempio:

from collections import defaultdict

d = defaultdict(list)

d['python'].append("awesome")

d['something-else'].append("not relevant")

d['python'].append("language")

for i in d.items():

    print i

Questo stampa:

('python', ['awesome', 'language'])
('something-else', ['not relevant'])

"Il tipo di dati dei campi valore viene specificato al momento dell'inizializzazione": questo non è corretto. Viene fornita una funzione factory di elemento. Ecco listla funzione da chiamare per inserire un valore mancante, non il tipo di oggetti da creare. Ad esempio, per avere un valore predefinito di 1, useresti lambda:1che ovviamente non è un tipo.
asac,

2

Penso che sia meglio usato al posto di un'istruzione case switch. Immagina se abbiamo un'istruzione case switch come di seguito:

option = 1

switch(option) {
    case 1: print '1st option'
    case 2: print '2nd option'
    case 3: print '3rd option'
    default: return 'No such option'
}

Non ci sono switchdichiarazioni di casi disponibili in Python. Possiamo ottenere lo stesso usando defaultdict.

from collections import defaultdict

def default_value(): return "Default Value"
dd = defaultdict(default_value)

dd[1] = '1st option'
dd[2] = '2nd option'
dd[3] = '3rd option'

print(dd[4])    
print(dd[5])    
print(dd[3])

Stampa:

Default Value
Default Value
3rd option

Nel frammento di cui sopra ddnon ha i tasti 4 o 5 e quindi stampa un valore predefinito che abbiamo configurato in una funzione di supporto. Questo è molto più bello di un dizionario non elaborato in cui KeyErrorviene lanciato a se la chiave non è presente. Da questo è evidente che defaultdictpiù come un'istruzione case switch in cui possiamo evitare if-elif-elif-elseblocchi complicati .

Un altro buon esempio che mi ha impressionato molto di questo sito è:

>>> from collections import defaultdict
>>> food_list = 'spam spam spam spam spam spam eggs spam'.split()
>>> food_count = defaultdict(int) # default value of int is 0
>>> for food in food_list:
...     food_count[food] += 1 # increment element's value by 1
...
defaultdict(<type 'int'>, {'eggs': 1, 'spam': 7})
>>>

Se proviamo ad accedere a elementi diversi da eggse spamotterremo un conteggio di 0.


2

Senza defaultdict, probabilmente puoi assegnare nuovi valori a chiavi invisibili ma non puoi modificarlo. Per esempio:

import collections
d = collections.defaultdict(int)
for i in range(10):
  d[i] += i
print(d)
# Output: defaultdict(<class 'int'>, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})

import collections
d = {}
for i in range(10):
  d[i] += i
print(d)
# Output: Traceback (most recent call last): File "python", line 4, in <module> KeyError: 0

2

Bene, defaultdict può anche aumentare il keyerror nel seguente caso:

    from collections import defaultdict
    d = defaultdict()
    print(d[3]) #raises keyerror

Ricorda sempre di dare argomento al defaultdict come defaultdict (int).


0

Il dizionario standard include il metodo setdefault () per recuperare un valore e stabilire un valore predefinito se il valore non esiste. Al contrario, defaultdict consente al chiamante di specificare il valore predefinito in anticipo quando il contenitore viene inizializzato.

import collections

def default_factory():
    return 'default value'

d = collections.defaultdict(default_factory, foo='bar')
print 'd:', d
print 'foo =>', d['foo']
print 'bar =>', d['bar']

Funziona bene finché è appropriato che tutte le chiavi abbiano lo stesso valore predefinito. Può essere particolarmente utile se il valore predefinito è un tipo utilizzato per aggregare o accumulare valori, come un elenco, un set o persino int. La documentazione della libreria standard include diversi esempi di utilizzo di defaultdict in questo modo.

$ python collections_defaultdict.py

d: defaultdict(<function default_factory at 0x100468c80>, {'foo': 'bar'})
foo => bar
bar => default value

0

In breve:

defaultdict(int) - l'argomento int indica che i valori saranno di tipo int.

defaultdict(list) - l'elenco degli argomenti indica che i valori saranno di tipo elenco.


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.