Est-il possible d'avoir une 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 sur mesure, en fonction des éléments x.a
et x.b
.
Je pourrais utiliser:
for x in stuff:
d[x.a,x.b] += x.c_int
mais alors je ne serais pas capable d'utiliser:
d.keys()
d[x.a].keys()
Oui comme ça:
defaultdict(lambda: defaultdict(int))
L'argument d'un defaultdict
(dans ce cas, lambda: defaultdict(int)
) sera appelé lorsque vous essayez d'accéder à une clé inexistante. La valeur de retour de celle-ci sera définie comme nouvelle valeur de cette clé, ce qui signifie dans notre cas que la valeur de d[Key_doesnt_exist]
sera defaultdict(int)
.
Si vous essayez d'accéder à une clé de ce dernier defaultdict, c'est-à-dire d[Key_doesnt_exist][Key_doesnt_exist]
, elle retournera 0, ce qui correspond à la valeur de retour de l'argument du dernier defaultdict, c'est-à-dire int()
.
Le paramètre du constructeur defaultdict est la fonction qui sera appelée pour construire de nouveaux éléments. Alors utilisons 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 un ne solution encore meilleure 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 de conteneur et Documentation Python - Collections
Je trouve un peu plus élégant d'utiliser partial
:
import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)
Bien sûr, c'est pareil qu'un lambda.
Pour référence, il est possible d'implémenter une méthode générique imbriquée defaultdict
par le biais de:
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 dictionnaires imbriqués avant que le type défini dans default_factory
ne soit utilisé. Par exemple:
my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')
D'autres ont répondu correctement à votre question sur la manière de faire fonctionner ce qui suit:
for x in stuff:
d[x.a][x.b] += x.c_int
Une alternative serait d'utiliser des n-uplets pour les clés:
d = defaultdict(int)
for x in stuff:
d[x.a,x.b] += x.c_int
# ^^^^^^^ Tuple key
La bonne chose à propos de cette approche est qu’elle est simple et peut être facilement étendue. Si vous avez besoin d'un mappage sur trois niveaux, utilisez simplement un tuple à trois éléments pour la clé.