Comment fonctionne collections.defaultdict?

532

J'ai lu les exemples dans les documents python, mais je n'arrive toujours pas à comprendre ce que cette méthode signifie. Quelqu'un peut-il aider? Voici deux exemples tirés des documents 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)]

et

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

les paramètres intet listpour quoi faire?

Lanston
la source
15
BTW, selon votre cas d'utilisation, n'oubliez pas de geler le defaultdict pour une utilisation en lecture seule en le définissant une default_factory = Nonefois que vous avez terminé de remplir le defaultdict. Voir cette question .
Acumenus

Réponses:

598

Habituellement, un dictionnaire Python lance un KeyErrorsi vous essayez d'obtenir un élément avec une clé qui n'est pas actuellement dans le dictionnaire. Le defaultdictcontraste créera simplement tous les éléments auxquels vous essayez d'accéder (à condition bien sûr qu'ils n'existent pas encore). Pour créer un tel élément "par défaut", il appelle l'objet fonction que vous passez au constructeur (plus précisément, il s'agit d'un objet "appelable" arbitraire, qui comprend des objets fonction et type). Pour le premier exemple, les éléments par défaut sont créés à l'aide de int(), qui renverra l'objet entier 0. Pour le deuxième exemple, les éléments par défaut sont créés à l'aide de list(), qui renvoie un nouvel objet de liste vide.

Sven Marnach
la source
4
Est-ce fonctionnellement différent de l'utilisation de d.get (key, default_val)?
Ambareesh
29
@Ambareesh d.get(key, default)ne modifiera jamais votre dictionnaire - il renverra simplement la valeur par défaut et laissera le dictionnaire inchangé. defaultdict, d'autre part, insérera une clé dans le dictionnaire si elle n'y est pas encore. C'est une grande différence; voir les exemples dans la question pour comprendre pourquoi.
Sven Marnach
Comment savons-nous quelle est la valeur par défaut pour chaque type? 0 pour int () et [] pour list () sont intuitifs, mais il peut également y avoir des types plus complexes ou auto-définis.
Sean
1
@Sean defaultdictappelle le constructeur que vous passez. Si vous passez un type a T, les valeurs seront construites à l'aide de T(). Tous les types ne peuvent pas être construits sans passer de paramètres. Si vous voulez construire un tel type, vous avez besoin d'une fonction wrapper, ou quelque chose comme functools.partial(T, arg1, arg2).
Sven Marnach
224

defaultdictsignifie que si une clé n'est pas trouvée dans le dictionnaire, au lieu d' KeyErrorêtre lancée, une nouvelle entrée est créée. Le type de cette nouvelle entrée est donné par l'argument de defaultdict.

Par exemple:

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

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0
orlp
la source
10
"Le type de cette nouvelle paire est donné par l'argument de defaultdict." Notez que l'argument peut être n'importe quel objet appelable - pas seulement des fonctions de type. Par exemple, si foo était une fonction qui renvoyait "bar", foo pourrait être utilisé comme argument pour dict par défaut et si une clé non présente était accessible, sa valeur serait définie sur "bar".
lf215
13
Ou si vous voulez juste retourner "bar": somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert
La quatrième ligne a renvoyé 0l'entier, s'il l'était, someddict = defaultdict(list)il revient [ ]. 0 est-il l'entier par défaut? Ou [] la liste par défaut?
Gathide
Ni. 0est immuable - dans CPython toutes les valeurs de -5à 256sont des singletons mis en cache mais c'est un comportement spécifique à l'implémentation - dans les deux cas, une nouvelle instance est "créée" à chaque fois avec int()ou list(). De cette façon, d[k].append(v)peut fonctionner sans remplir le dictionnaire avec des références à la même liste, ce qui rendrait defaultdictpresque inutile. Si tel était le comportement, defaultdictprendrait une valeur, pas un lambda, comme paramètre. (Désolé pour la terrible explication!)
wizzwizz4
93

defaultdict

"Le dictionnaire standard inclut la méthode setdefault () pour récupérer une valeur et établir une valeur par défaut si la valeur n'existe pas. En revanche, defaultdictpermet à l'appelant de spécifier la valeur par défaut (valeur à renvoyer) à l'avance lorsque le conteneur est initialisé."

tel que défini par Doug Hellmann dans The Python Standard Library by Example

Comment utiliser defaultdict

Importer defaultdict

>>> from collections import defaultdict

Initialiser defaultdict

Initialisez-le en passant

appelable comme premier argument (obligatoire)

>>> 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 comme deuxième argument (facultatif)

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

ou

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

Comment ça fonctionne

Comme une classe enfant de dictionnaire standard, elle peut exécuter toutes les mêmes fonctions.

Mais en cas de passage d'une clé inconnue, il renvoie la valeur par défaut au lieu de l'erreur. Par exemple:

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

Si vous souhaitez modifier la valeur par défaut, remplacez 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})

ou

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

Exemples dans la question

Exemple 1

Comme int a été passé en tant que default_factory, toute clé inconnue renverra 0 par défaut.

Maintenant que la chaîne est passée dans la boucle, cela augmentera le nombre de ces alphabets en 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})

Exemple 2

Comme une liste a été passée en tant que default_factory, toute clé inconnue (inexistante) renverra [] (ie. List) par défaut.

Maintenant que la liste des tuples est passée dans la boucle, elle ajoutera la valeur dans le d [color]

>>> 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]})
Somendra Joshi
la source
20

Les dictionnaires sont un moyen pratique de stocker des données pour une récupération ultérieure par nom (clé). Les clés doivent être des objets uniques et immuables, et sont généralement des chaînes. Les valeurs d'un dictionnaire peuvent être n'importe quoi. Pour de nombreuses applications, les valeurs sont des types simples tels que des entiers et des chaînes.

Cela devient plus intéressant lorsque les valeurs d'un dictionnaire sont des collections (listes, dict, etc.) Dans ce cas, la valeur (une liste ou un dict vide) doit être initialisée la première fois qu'une clé donnée est utilisée. Bien que cela soit relativement facile à faire manuellement, le type defaultdict automatise et simplifie ces types d'opérations. Un defaultdict fonctionne exactement comme un dict normal, mais il est initialisé avec une fonction («usine par défaut») qui ne prend aucun argument et fournit la valeur par défaut pour une clé inexistante.

Un défaut ne déclenchera jamais une erreur de clé. Toute clé qui n'existe pas obtient la valeur renvoyée par la fabrique par défaut.

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

Voici un autre exemple sur Comment utiliser defaultdict, nous pouvons réduire la complexité

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

En conclusion, chaque fois que vous avez besoin d'un dictionnaire et que la valeur de chaque élément doit commencer par une valeur par défaut, utilisez un defaultdict.

dimension
la source
18

Il y a une grande explication des défauts par défaut ici: http://ludovf.net/blog/python-collections-defaultdict/

Fondamentalement, les paramètres int et list sont des fonctions que vous passez. N'oubliez pas que Python accepte les noms de fonction comme arguments. int renvoie 0 par défaut et list renvoie une liste vide lorsqu'elle est appelée avec des parenthèses.

Dans les dictionnaires normaux, si dans votre exemple j'essaie d'appeler d[a], j'obtiendrai une erreur (KeyError), car seules les clés m, s, i et p existent et la clé a n'a pas été initialisée. Mais dans un dict par défaut, il prend un nom de fonction comme argument, lorsque vous essayez d'utiliser une clé qui n'a pas été initialisée, il appelle simplement la fonction que vous avez transmise et attribue sa valeur de retour comme valeur de la nouvelle clé.

varagrawal
la source
7

Étant donné que la question porte sur «comment cela fonctionne», certains lecteurs voudront peut-être voir plus d'écrous et de boulons. Plus précisément, la méthode en question est la __missing__(key)méthode. Voir: https://docs.python.org/2/library/collections.html#defaultdict-objects .

Plus concrètement, cette réponse montre comment utiliser de __missing__(key)manière pratique: https://stackoverflow.com/a/17956989/1593924

Pour clarifier ce que signifie «appelable», voici une session interactive (à partir de 2.7.6 mais devrait également fonctionner en 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})

C'était l'utilisation la plus courante de defaultdict (à l'exception de l'utilisation inutile de la variable x). Vous pouvez faire la même chose avec 0 comme valeur par défaut explicite, mais pas avec une valeur simple:

>>> dd2 = defaultdict(0)

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

Au lieu de cela, ce qui suit fonctionne car il transmet une fonction simple (il crée à la volée une fonction sans nom qui ne prend aucun argument et renvoie toujours 0):

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

Et avec une valeur par défaut différente:

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

Mon propre 2 ¢: vous pouvez également sous-classer defaultdict:

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

Cela pourrait être utile pour des cas très complexes.

Edward Falk
la source
4

Le comportement de defaultdictpeut être facilement imité en utilisant dict.setdefaultau lieu de d[key]dans chaque appel.

En d'autres termes, le code:

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]

est équivalent à:

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]

La seule différence est que, en utilisant defaultdict, le constructeur de liste n'est appelé qu'une seule fois, et en utilisant dict.setdefaultle constructeur de liste est appelé plus souvent (mais le code peut être réécrit pour éviter cela, si vraiment nécessaire).

Certains diront qu'il y a une considération de performance, mais ce sujet est un champ de mines. Cet article montre qu'il n'y a pas de gros gain de performances dans l'utilisation de defaultdict, par exemple.

OMI, defaultdict est une collection qui ajoute plus de confusion que d'avantages au code. Inutile pour moi, mais d'autres peuvent penser différemment.

Diego Queiroz
la source
3

L'outil defaultdict est un conteneur de la classe collections de Python. Il est similaire au conteneur de dictionnaire (dict) habituel, mais il a une différence: le type de données des champs de valeur est spécifié lors de l'initialisation.

Par exemple:

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

Cela imprime:

('python', ['awesome', 'language'])
('something-else', ['not relevant'])
saarthak johari
la source
"Le type de données des champs de valeur est spécifié lors de l'initialisation": ce n'est pas correct. Une fonction d'usine d'élément est fournie. Voici listla fonction à appeler pour remplir une valeur manquante, pas le type des objets à créer. Par exemple, pour avoir une valeur par défaut de 1, vous utiliseriez lambda:1ce qui n'est évidemment pas un type.
asac
2

Je pense que son mieux utilisé à la place d'une déclaration de cas de commutation. Imaginez si nous avons une déclaration de cas de commutation comme ci-dessous:

option = 1

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

Il n'y a pas d' switchinstructions de cas disponibles en python. Nous pouvons obtenir le même résultat en utilisant 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])

Il imprime:

Default Value
Default Value
3rd option

Dans l'extrait ci-dessus ddn'a pas de clés 4 ou 5 et donc il imprime une valeur par défaut que nous avons configurée dans une fonction d'assistance. C'est bien plus agréable qu'un dictionnaire brut où un KeyErrorest lancé si la clé n'est pas présente. De cela, il est évident que defaultdictplus comme une déclaration de cas de commutation où nous pouvons éviter un if-elif-elif-elsebloc compliqué .

Un autre bon exemple qui m'a beaucoup impressionné sur ce site est:

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

Si nous essayons d'accéder à des éléments autres que eggset spamnous obtiendrons un compte de 0.

Swadhikar C
la source
2

Sans defaultdict, vous pouvez probablement affecter de nouvelles valeurs à des clés invisibles mais vous ne pouvez pas les modifier. Par exemple:

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
Ming Liu
la source
2

Eh bien, defaultdict peut également augmenter l'erreur de clé dans le cas suivant:

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

N'oubliez pas de donner un argument à defaultdict comme defaultdict (int).

Shweta Sharma
la source
0

Le dictionnaire standard inclut la méthode setdefault () pour récupérer une valeur et établir une valeur par défaut si la valeur n'existe pas. En revanche, defaultdict permet à l'appelant de spécifier la valeur par défaut dès que le conteneur est initialisé.

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

Cela fonctionne bien tant qu'il est approprié que toutes les clés aient la même valeur par défaut. Cela peut être particulièrement utile si la valeur par défaut est un type utilisé pour agréger ou accumuler des valeurs, comme une liste, un ensemble ou même un entier. La documentation standard de la bibliothèque comprend plusieurs exemples d'utilisation de defaultdict de cette façon.

$ python collections_defaultdict.py

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

la source
0

En bref:

defaultdict(int) - l'argument int indique que les valeurs seront de type int.

defaultdict(list) - la liste d'arguments indique que les valeurs seront de type liste.

Shravan kp
la source