Mappage de valeurs dans un dictionnaire python

243

Étant donné un dictionnaire que { k1: v1, k2: v2 ... }je veux obtenir, { k1: f(v1), k2: f(v2) ... }je passe une fonction f.

Existe-t-il une telle fonction intégrée? Ou dois-je faire

dict([(k, f(v)) for (k, v) in my_dictionary.iteritems()])

Idéalement, j'écrirais

my_dictionary.map_values(f)

ou

my_dictionary.mutate_values_with(f)

Autrement dit, peu importe que le dictionnaire d'origine soit muté ou qu'une copie soit créée.

Tarrasch
la source
2
Une meilleure façon d'écrire votre exemple serait dict((k, f(v)) for k, v in mydict.iteritems()), c'est-à-dire sans les crochets, d'empêcher la création d'une liste intermédiaire via un générateur.
bereal

Réponses:

355

Il n'y a pas une telle fonction; la façon la plus simple de le faire est d'utiliser une compréhension dict:

my_dictionary = {k: f(v) for k, v in my_dictionary.items()}

En python 2.7, utilisez la .iteritems()méthode au lieu de .items()pour économiser de la mémoire. La syntaxe de compréhension de dict n'a pas été introduite avant Python 2.7.

Notez qu'il n'y a pas non plus une telle méthode sur les listes; il faudrait utiliser une compréhension de liste ou la map()fonction.

En tant que tel, vous pouvez également utiliser la map()fonction pour traiter votre dict:

my_dictionary = dict(map(lambda kv: (kv[0], f(kv[1])), my_dictionary.iteritems()))

mais ce n'est pas vraiment lisible.

Martijn Pieters
la source
5
+1: c'est ce que je ferais aussi. dict(zip(a, map(f, a.values())))est légèrement plus court, mais je dois penser à ce qu'il fait et me rappeler que oui, les clés et les valeurs sont itérées dans le même ordre si le dict ne change pas. Je n'ai pas du tout à penser à ce que fait le dictcomp, et c'est donc la bonne réponse.
DSM
2
@chiborg: c'est parce que plutôt que de rechercher toutes les paires clé-valeur en une seule fois, vous utilisez maintenant des my_dictionary.__getitem__appels de nombre de clés .
Martijn Pieters
1
Notez que depuis PEP3113 (implémenté en python 3.x) les paramètres de tuple ne sont plus pris en charge: lambda (k,v): (k, f(v))doit être réécrit dans quelque chose commelambda k_v: (k_v[0], f(k_v[1]))
normanius
1
Pourquoi le déballage des paramètres a-t-il été interrompu? En quoi est-ce une amélioration ?
javadba
3
venant d'un langage FP, Python semblerait incroyablement maladroit.
juanchito
22

Vous pouvez le faire sur place, plutôt que de créer un nouveau dict, ce qui peut être préférable pour les grands dictionnaires (si vous n'avez pas besoin d'une copie).

def mutate_dict(f,d):
    for k, v in d.iteritems():
        d[k] = f(v)

my_dictionary = {'a':1, 'b':2}
mutate_dict(lambda x: x+1, my_dictionary)

résulte en my_dictionarycontenant:

{'a': 2, 'b': 3}
gens
la source
1
Cool, vous devriez peut - être renommer mapdictà mutate_values_withou quelque chose pour le rendre clair que vous réécrivez le dict! :)
Tarrasch
2
zip(d.keys(), d.values())fonctionne pour plus de versions au lieu deiteritems()
ytpillai
1
@ytpillai 'zip' ou compréhensions font une copie, plutôt que de changer les valeurs en place, ce qui est le but de ma réponse. La réponse acceptée est la meilleure pour quand une copie est correcte.
gens
1
Mes excuses, je ne savais pas que vous vouliez utiliser la méthode des éléments. Cependant, une autre amélioration est également possible (pour les utilisateurs non Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Économise de l'espace en créant un itérateur
ytpillai
13

En raison de PEP-0469 qui a renommé iteritems () en items () et PEP-3113 qui a supprimé le décompactage des paramètres Tuple , dans Python 3.x, vous devriez écrire Martijn Pieters ♦ répondre comme ceci:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
lucidyan
la source
4

Bien que ma réponse d'origine ait manqué le point (en essayant de résoudre ce problème avec la solution de la clé d'accès dans l'usine de defaultdict ), je l'ai retravaillée pour proposer une solution réelle à la présente question.

C'est ici:

class walkableDict(dict):
  def walk(self, callback):
    try:
      for key in self:
        self[key] = callback(self[key])
    except TypeError:
      return False
    return True

Usage:

>>> d = walkableDict({ k1: v1, k2: v2 ... })
>>> d.walk(f)

L'idée est de sous-classer le dict original pour lui donner la fonctionnalité souhaitée: "mapper" une fonction sur toutes les valeurs.

Le point positif est que ce dictionnaire peut être utilisé pour stocker les données d'origine comme s'il s'agissait d'un dict, tout en transformant toutes les données sur demande avec un rappel.

Bien sûr, n'hésitez pas à nommer la classe et la fonction comme vous le souhaitez (le nom choisi dans cette réponse est inspiré par la array_walk()fonction de PHP ).

Remarque: Ni le bloc try- exceptni les returninstructions ne sont obligatoires pour la fonctionnalité, ils sont là pour imiter davantage le comportement des PHP array_walk.

7heo.tk
la source
1
Cela ne résout pas la question OP car la __missing__méthode ne sera pas appelée pour les clés existantes, que nous voulons transformer, sauf si la méthode d'usine passée utilise le dict d'origine comme une solution de repli, mais comme cela ne fait pas partie de l'exemple d'utilisation, Je considère que c'est une réponse insatisfaisante au problème actuel.
Kaos
Quelles clés existantes?
7heo.tk
De l'OP: Given a dictionary { k1: v1, k2: v2 ... } .... Autrement dit, vous avez déjà un dictpour commencer ..
Kaos
Je voudrais dire que nous avons tous les deux raison; mais je crois que nous nous trompons tous les deux. Vous avez raison en ce que ma réponse ne répond pas à la question; mais pas pour la raison que vous avez invoquée. J'ai simplement raté le point, donnant un moyen d'obtenir {v1: f(v1), v2: f(v2), ...}donné [v1, v2, ...], et non donné un dict. Je vais modifier ma réponse pour corriger cela.
7heo.tk
2

Pour éviter de faire de l'indexation à l'intérieur de lambda, comme:

rval = dict(map(lambda kv : (kv[0], ' '.join(kv[1])), rval.iteritems()))

Vous pouvez également faire:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
Votre sincèrement
la source
C'est une manipulation intelligente dans le 2-tuple lui-même dans le deuxième exemple. Cependant, il utilise le décompactage automatique de tuple dans le lambda, qui n'est plus pris en charge dans Python 3. Par conséquent lambda(k,v), ne fonctionnera pas. Voir stackoverflow.com/questions/21892989/…
Jonathan Komar
0

Je viens juste de traverser ce cas d'utilisation. J'ai implémenté la réponse de gens , en ajoutant une approche récursive pour gérer les valeurs qui sont également des dict:

def mutate_dict_in_place(f, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            mutate_dict_in_place(f, v)
        else:
            d[k] = f(v)

# Exemple handy usage
def utf8_everywhere(d):
    mutate_dict_in_place((
        lambda value:
            value.decode('utf-8')
            if isinstance(value, bytes)
            else value
        ),
        d
    )

my_dict = {'a': b'byte1', 'b': {'c': b'byte2', 'd': b'byte3'}}
utf8_everywhere(my_dict)
print(my_dict)

Cela peut être utile lorsque vous traitez des fichiers json ou yaml qui codent des chaînes en octets dans Python 2

Oyono
la source