446 votes

defaultdict de defaultdict ?

Existe-t-il un moyen d'avoir un defaultdict(defaultdict(int)) afin de faire fonctionner le code suivant ?

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

d doit être construit de manière ad-hoc, en fonction de x.a y x.b éléments.

Je pourrais utiliser :

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

mais alors je ne pourrais pas l'utiliser :

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

7 votes

Voir question similaire Quelle est la meilleure façon d'implémenter des dictionnaires imbriqués en Python ? . Vous trouverez également des informations utiles dans l'article de Wikipedia sur Autovivification .

797voto

mouad Points 21520

Oui, comme ça :

defaultdict(lambda: defaultdict(int))

L'argument d'un defaultdict (dans ce cas, c'est lambda: defaultdict(int) ) sera appelé lorsque vous essayez d'accéder à une clé qui n'existe pas. La valeur de retour sera définie comme la nouvelle valeur de cette clé, ce qui signifie dans notre cas la valeur de d[Key_doesnt_exist] sera defaultdict(int) .

Si vous essayez d'accéder à une clé à partir de ce dernier defaultdict, c'est à dire d[Key_doesnt_exist][Key_doesnt_exist] il retournera 0, qui est la valeur de retour de l'argument du dernier defaultdict, c'est-à-dire int() .

8 votes

Cela fonctionne très bien ! pourriez-vous expliquer le rationnel derrière cette syntaxe ?

45 votes

@Jonathan : Oui bien sûr, l'argument d'un defaultdict (dans ce cas, c'est lambda : defaultdict(int) ) sera appelé lorsque vous essayez d'accéder à une clé qui n'existe pas et la valeur de retour sera définie comme la nouvelle valeur de cette clé, ce qui signifie dans notre cas la valeur de d[Key_dont_exist] sera defaultdict(int) et si vous essayez d'accéder à une clé à partir de ce dernier defaultdict, c'est-à-dire d[Key_dont_exist][Key_dont_exist] il retournera 0 qui est la valeur de retour de l'argument du dernier defaultdict c'est-à-dire int() J'espère que cela a été utile.

32 votes

L'argument de defaultdict devrait être une fonction. defaultdict(int) est un dictionnaire, tandis que lambda: defaultdict(int) est une fonction qui renvoie un dictionnaire.

57voto

yanjost Points 1788

Le paramètre du constructeur de defaultdict est la fonction qui sera appelée pour construire de nouveaux éléments. Utilisons donc un lambda !

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

Depuis Python 2.7, il existe une fonction solution encore meilleure en utilisant 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})

Quelques bonus

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

Pour plus d'informations, voir PyMOTW - Collections - Types de données des conteneurs y Documentation Python - collections

8 votes

Pour boucler le cercle ici, vous devriez utiliser d = defaultdict(lambda : Counter()) plutôt que d = defaultdict(lambda : defaultdict(int)) pour traiter spécifiquement le problème tel qu'il a été posé à l'origine.

4 votes

@gumption vous pouvez simplement utiliser d = defaultdict(Counter()) pas besoin d'un lambda dans ce cas

9 votes

@Deb vous avez une petite erreur - enlevez les parenthèses intérieures pour passer un callable au lieu d'un Counter objet. C'est-à-dire : d = defaultdict(Counter)

40voto

Clément Points 1439

Les réponses précédentes ont traité de la façon de faire un système à deux niveaux ou à n niveaux. defaultdict . Dans certains cas, vous voulez une infinité :

def ddict():
    return defaultdict(ddict)

Utilisation :

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

10 votes

J'adore ça. C'est diablement simple, mais incroyablement utile. Merci !

0 votes

Comment cela peut-il être utilisé pour le cas suivant : le nième niveau doit être un int ou une liste.

1 votes

@MonsieurBeilto "les réponses précédentes ont abordé la façon de faire un defaultdict à deux niveaux ou à n niveaux". stackoverflow.com/a/54266841/695591

33voto

katrielalex Points 40655

Je trouve qu'il est légèrement plus élégant d'utiliser partial :

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

Bien sûr, c'est la même chose qu'un lambda.

3 votes

Partial est également meilleur que lambda ici parce qu'il peut être appliqué récursivement :) voir ma réponse ci-dessous pour une méthode générique imbriquée de factory defaultdict.

0 votes

@Campi vous n'avez pas besoin de partiel pour les applications récursives, AFAICT

17voto

Campi Points 170

À titre de référence, il est possible de mettre en œuvre un système générique imbriqué. defaultdict méthode d'usine à travers :

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

La profondeur définit le nombre de dictionnaire imbriqué avant le type défini dans default_factory est utilisé. Par exemple :

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

0 votes

Pouvez-vous donner un exemple d'utilisation ? Le système ne fonctionne pas comme je l'attendais. ndd = nested_defaultdict(dict) .... ndd['a']['b']['c']['d'] = 'e' jette KeyError: 'b'

0 votes

Hey David, vous devez définir la profondeur de votre dictionnaire, dans votre exemple 3 (comme vous avez défini le default_factory pour être un dictionnaire aussi. nested_defaultdict(dict, 3) fonctionnera pour vous.

0 votes

C'était super utile, merci ! Une chose que j'ai remarquée est que cela crée un default_dict à depth=0 ce qui n'est pas toujours souhaitable si la profondeur est inconnue au moment de l'appel. Facile à corriger en ajoutant une ligne if not depth: return default_factory() au début de la fonction, bien qu'il existe probablement une solution plus élégante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X