Grâce à de grands spécialistes de SO, j'ai découvert les possibilités offertes par collections.defaultdict
, notamment en lisibilité et rapidité. Je les ai mis à profit avec succès.
Maintenant, j'aimerais implémenter trois niveaux de dictionnaires, les deux premiers étant defaultdict
et le plus bas étant int
. Je ne trouve pas le moyen approprié de le faire. Voici ma tentative:
from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
("key2", {"a1":32, "a2":55}),
("key3", {"a1":43, "a2":44})]
for i in a:
d[i[0]] = i[1]
Maintenant, cela fonctionne, mais le comportement suivant, qui correspond au comportement souhaité, ne fonctionne pas:
d["key4"]["a1"] + 1
Je suppose que j'aurais dû déclarer quelque part que le deuxième niveau defaultdict
est de type int
, mais je n'ai pas trouvé où ni comment le faire.
La raison pour laquelle j'utilise defaultdict
en premier lieu est d'éviter d'avoir à initialiser le dictionnaire pour chaque nouvelle clé.
Une suggestion plus élégante?
Merci pythoneers!
Utilisation:
d = defaultdict(lambda: defaultdict(int))
Cela créera une nouvelle defaultdict(int)
chaque fois qu'une nouvelle clé sera utilisée dans d
.
Une autre façon de créer un defaultdict imbriqué et picklable consiste à utiliser un objet partiel au lieu d'un lambda:
from functools import partial
...
d = defaultdict(partial(defaultdict, int))
Cela fonctionnera car la classe defaultdict est globalement accessible au niveau du module:
"Vous ne pouvez décaper un objet partiel que si la fonction [ou, dans ce cas, la classe] qu’il recouvre est globalement accessible ... sous son __name__ (dans son __module__)" - fonctions partielles enveloppées dans le pickling
Regardez la réponse de nosklo ici pour une solution plus générale.
class AutoVivification(dict):
"""Implementation of Perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Essai:
a = AutoVivification()
a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6
print a
Sortie:
{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
Comme demandé par @ rschwieb pour D['key'] += 1
, Nous pouvons développer previous en surchargeant l'ajout en définissant la méthode __add__
, Pour que cela se comporte davantage comme un collections.Counter()
D'abord, __missing__
Sera appelé pour créer une nouvelle valeur vide, qui sera passée dans __add__
. Nous testons la valeur, en comptant sur les valeurs vides pour être False
.
Voir émulation de types numériques pour plus d'informations sur le remplacement.
from numbers import Number
class autovivify(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition for numeric types when self is empty """
if not self and isinstance(x, Number):
return x
raise ValueError
def __sub__(self, x):
if not self and isinstance(x, Number):
return -1 * x
raise ValueError
Exemples:
>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}
Plutôt que de vérifier l'argument comme un nombre (très non python, amirite!), Nous pourrions simplement fournir une valeur par défaut de 0, puis tenter l'opération:
class av2(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition when self is empty """
if not self:
return 0 + x
raise ValueError
def __sub__(self, x):
""" override subtraction when self is empty """
if not self:
return 0 - x
raise ValueError
En retard pour le parti, mais pour une profondeur arbitraire, je me suis retrouvé à faire quelque chose comme ceci:
from collections import defaultdict
class DeepDict(defaultdict):
def __call__(self):
return DeepDict(self.default_factory)
Le truc ici consiste essentiellement à faire de l’instance DeepDict
une fabrique valide pour la construction des valeurs manquantes. Maintenant nous pouvons faire des choses comme
dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2]) # 7
ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3]) # 9
def _sub_getitem(self, k):
try:
# sub.__class__.__bases__[0]
real_val = self.__class__.mro()[-2].__getitem__(self, k)
val = '' if real_val is None else real_val
except Exception:
val = ''
real_val = None
# isinstance(Avoid,dict)也是true,会一直递归死
if type(val) in (dict, list, str, Tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
# 重新赋值当前字典键为返回值,当对其赋值时可回溯
if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
self[k] = val
return val
def _sub_pop(self, k=-1):
try:
val = self.__class__.mro()[-2].pop(self, k)
val = '' if val is None else val
except Exception:
val = ''
if type(val) in (dict, list, str, Tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
return val
class DefaultDict(dict):
def __getitem__(self, k):
return _sub_getitem(self, k)
def pop(self, k):
return _sub_pop(self, k)
In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''
Pas d'erreur encore. Peu importe le nombre de niveaux imbriqués. pop pas d'erreur aussi
dd = DefaultDict ({"1": 333333})