J'ai une grande liste comme:
[A][B1][C1]=1
[A][B1][C2]=2
[A][B2]=3
[D][E][F][G]=4
Je veux construire un dict multi-niveaux comme:
A
--B1
-----C1=1
-----C2=1
--B2=3
D
--E
----F
------G=4
Je sais que si j'utilise defaultdict récursif, je peux écrire table[A][B1][C1]=1
, table[A][B2]=2
, mais cela ne fonctionne que si je code ces instructions avec insert.
Lors de l'analyse de la liste, je ne sais pas combien de [] il me faut au préalable d'appeler table[key1][key2][...]
.
Vous pouvez le faire sans même définir une classe:
from collections import defaultdict
nested_dict = lambda: defaultdict(nested_dict)
nest = nested_dict()
nest[0][1][2][3][4][5] = 6
Votre exemple indique que, à n'importe quel niveau, il peut y avoir une valeur, ainsi qu'un dictionnaire de sous-éléments. C'est ce qu'on appelle un tree , et de nombreuses implémentations sont disponibles pour eux. C'est un:
from collections import defaultdict
class Tree(defaultdict):
def __init__(self, value=None):
super(Tree, self).__init__(Tree)
self.value = value
root = Tree()
root.value = 1
root['a']['b'].value = 3
print root.value
print root['a']['b'].value
print root['c']['d']['f'].value
Les sorties:
1
3
None
Vous pouvez faire quelque chose de similaire en écrivant l'entrée en JSON et en utilisant json.load
pour la lire comme une structure de dictionnaires imbriqués.
Je le ferais avec une sous-classe de dict
qui définit __missing__
:
>>> class NestedDict(dict):
... def __missing__(self, key):
... self[key] = NestedDict()
... return self[key]
...
>>> table = NestedDict()
>>> table['A']['B1']['C1'] = 1
>>> table
{'A': {'B1': {'C1': 1}}}
Vous ne pouvez pas le faire directement avec defaultdict car defaultdict attend la fonction factory au moment de l'initialisation, mais au moment de l'initialisation, il n'est pas possible de décrire le même defaultdict. La construction ci-dessus fait la même chose que dict par défaut, mais comme il s'agit d'une classe nommée (NestedDict), elle peut se référer à elle-même en cas de découverte de clés manquantes. Il est également possible de sous-classer defaultdict et de remplacer __init__
.
Je pense que la plus simple implémentation d’un dictionnaire récursif est la suivante. Seuls les nœuds terminaux peuvent contenir des valeurs.
# Define recursive dictionary
tree = lambda: defaultdict(tree)
Usage:
# Create instance
mydict = tree()
tree['a'] = 1
tree['b']['a'] = 2
tree['c']
tree['d']['a']['b'] = 0
# Print
import prettyprint
prettyprint.pp(tree)
Sortie:
{
"a": 1,
"b": {
"a": 1
},
"c": {},
"d": {
"a": {
"b": 0
}
}
}
Ceci est équivalent à ce qui précède, mais en évitant la notation lambda. Peut-être plus facile à lire?
def dict_factory():
return defaultdict(dict_factory)
your_dict = dict_factory()
De plus, à partir des commentaires, si vous souhaitez mettre à jour un dict existant, vous pouvez simplement
your_dict[0][1][2].update({"some_key":"some_value"})
Afin d'ajouter des valeurs au dict.
Dan O'Huiginn a publié une solution très agréable sur son journal en 2010:
http://ohuiginn.net/mt/2010/07/nested_dictionaries_in_python.html
>>> class NestedDict(dict):
... def __getitem__(self, key):
... if key in self: return self.get(key)
... return self.setdefault(key, NestedDict())
>>> eggs = NestedDict()
>>> eggs[1][2][3][4][5]
{}
>>> eggs
{1: {2: {3: {4: {5: {}}}}}}
Une possibilité légèrement différente qui permet l'initialisation régulière du dictionnaire:
from collections import defaultdict
def superdict(arg=()):
update = lambda obj, arg: obj.update(arg) or obj
return update(defaultdict(superdict), arg)
Exemple:
>>> d = {"a":1}
>>> sd = superdict(d)
>>> sd["b"]["c"] = 2
Pour ajouter à @ Hugo
Pour avoir une profondeur maximale:
l=lambda x:defaultdict(lambda:l(x-1)) if x>0 else defaultdict(dict)
arr = l(2)
Vous pouvez y parvenir avec une variable defaultdict
récurrente.
from collections import defaultdict
def tree():
def the_tree():
return defaultdict(the_tree)
return the_tree()
Il est important de protéger le nom d'usine par défaut, the_tree
, ici, lors d'une fermeture (étendue de la fonction locale "privée"). Évitez d'utiliser une version lambda
one-liner, qui est perturbée en raison des fermetures de liaisons late de Python , et implémentez-la avec un def
à la place.
La réponse acceptée, en utilisant un lambda, a une faille dans laquelle des instances doivent s'appuyer sur le nom nested_dict
existant dans une portée externe. Si, pour une raison quelconque, le nom de la fabrique ne peut pas être résolu (par exemple, il a été rebondi ou supprimé), les instances préexistantes seront également subtilement cassées:
>>> nested_dict = lambda: defaultdict(nested_dict)
>>> nest = nested_dict()
>>> nest[0][1][2][3][4][6] = 7
>>> del nested_dict
>>> nest[8][9] = 10
# NameError: name 'nested_dict' is not defined