222 votes

Nested defaultdict de defaultdict

Existe-t-il un moyen de faire en sorte qu'un defaultdict soit également le défaut pour le defaultdict ? (c'est-à-dire un defaultdict récursif à niveau infini?)

Je veux être capable de faire :

x = defaultdict(...truc...)
x[0][1][0]
{}

Donc, je peux faire x = defaultdict(defaultdict), mais ce n'est que pour un deuxième niveau :

x[0]
{}
x[0][0]
KeyError: 0

Il existe des recettes pour faire cela. Mais est-il possible de le faire simplement en utilisant les arguments normaux de defaultdict ?

Notez que cette question porte sur la façon de créer un defaultdict récursif à niveau infini, donc elle est distincte de Python: defaultdict of defaultdict?, qui traitait de la façon de créer un defaultdict à deux niveaux.

Je finirai probablement par utiliser le motif bunch, mais lorsque j'ai réalisé que je ne savais pas faire cela, cela m'a intéressé.

324voto

Chris W. Points 7182

Les autres réponses ici vous montrent comment créer un defaultdict qui contient "un nombre infini" de defaultdict, mais elles ne traitent pas ce qui aurait pu être votre besoin initial qui était tout simplement d'avoir un defaultdict à deux niveaux.

Vous cherchiez peut-être :

defaultdict(lambda: defaultdict(dict))

Les raisons pour lesquelles vous pourriez préférer cette construction sont :

  • Elle est plus explicite que la solution récursive, et par conséquent probablement plus compréhensible pour le lecteur.
  • Cela permet à la "feuille" du defaultdict d'être autre chose qu'un dictionnaire, par exemple : defaultdict(lambda: defaultdict(list)) ou defaultdict(lambda: defaultdict(set))

260voto

Andrew Clark Points 77748

Pour un nombre arbitraire de niveaux :

def rec_dd():
    return defaultdict(rec_dd)

>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

Bien sûr, vous pourriez également le faire avec une lambda, mais je trouve les lambdas moins lisibles. Dans tous les cas, cela ressemblerait à ceci :

rec_dd = lambda: defaultdict(rec_dd)

91voto

BrenBarn Points 63718

Il y a une astuce astucieuse pour faire cela:

arbre = lambda: defaultdict(arbre)

Ensuite, vous pouvez créer votre x avec x = arbre().

23voto

pts Points 15396

Similaire à la solution de BrenBarn, mais ne contient pas le nom de la variable tree deux fois, donc ça fonctionne même après des modifications sur le dictionnaire de variables :

tree = (lambda f: f(f))(lambda a: (lambda: defaultdict(a(a))))

Ensuite, vous pouvez créer chaque nouveau x avec x = tree().


Pour la version def, on peut utiliser la portée de fermeture de fonction pour protéger la structure de données du défaut où les instances existantes arrêtent de fonctionner si le nom tree est réassigné. Ça ressemble à ça :

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()

23voto

Стас Цепа Points 220

Je proposerais également une implémentation plus orientée objet (OOP), qui prend en charge un nesting infini ainsi qu'une représentation repr correctement formatée.

class NestedDefaultDict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)

    def __repr__(self):
        return repr(dict(self))

Utilisation :

my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']

print(my_dict)  # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}

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