Asignación de valores en un diccionario de Python

243

Dado un diccionario { k1: v1, k2: v2 ... }que quiero obtener { k1: f(v1), k2: f(v2) ... }siempre que pase una función f.

¿Existe alguna función incorporada? O tengo que hacer

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

Lo ideal sería escribir

my_dictionary.map_values(f)

o

my_dictionary.mutate_values_with(f)

Es decir, no me importa si el diccionario original está mutado o si se crea una copia.

Tarrasch
fuente
2
Una mejor manera de escribir su ejemplo sería dict((k, f(v)) for k, v in mydict.iteritems()), es decir, sin los corchetes, que evitaría la creación de una lista intermedia a través de un generador.
bereal

Respuestas:

354

No hay tal función; La forma más fácil de hacer esto es usar una comprensión dict:

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

En python 2.7, use el .iteritems()método en lugar de .items()guardar memoria. La sintaxis de comprensión dict no se introdujo hasta Python 2.7.

Tenga en cuenta que tampoco existe tal método en las listas; tendrías que usar una lista de comprensión o la map()función.

Como tal, también podría usar la map()función para procesar su dict:

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

pero eso no es tan legible, de verdad.

Martijn Pieters
fuente
55
+1: esto es lo que yo haría también. dict(zip(a, map(f, a.values())))es marginalmente más corto, pero tengo que pensar en lo que está haciendo y recordarme que sí, las claves y los valores se repiten en el mismo orden si el dict no cambia. No tengo que pensar en absoluto sobre lo que está haciendo el dictcomp, por lo que es la respuesta correcta.
DSM
2
@chiborg: eso es porque en lugar de buscar todos los pares clave-valor de una vez, ahora está utilizando my_dictionary.__getitem__llamadas de número de claves .
Martijn Pieters
1
Tenga en cuenta que dado que PEP3113 (implementado en python 3.x) los parámetros de tupla ya no son compatibles: lambda (k,v): (k, f(v))debe reescribirse en algo comolambda k_v: (k_v[0], f(k_v[1]))
normanius el
1
¿Por qué el desempaquetado de parámetros se rechazó? ¿Cómo es eso una mejora ?
javadba
3
viniendo de un lenguaje FP, Python parecería increíblemente incómodo.
juanchito
21

Puede hacer esto en el lugar, en lugar de crear un nuevo dict, que puede ser preferible para diccionarios grandes (si no necesita una copia).

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)

resulta en my_dictionarycontener:

{'a': 2, 'b': 3}
gens
fuente
1
Fresco, se debe tal vez cambiar el nombre mapdictde mutate_values_witho algo para dejar muy claro que reescribir el dict! :)
Tarrasch
2
zip(d.keys(), d.values())funciona para más versiones en lugar deiteritems()
ytpillai
1
@ytpillai 'zip' o las comprensiones hacen una copia, en lugar de cambiar los valores en el lugar, que es el propósito de mi respuesta. La respuesta aceptada es la mejor para cuando una copia está bien.
Gens
1
Mis disculpas, no me di cuenta de que querías usar el método de artículos. Sin embargo, otra mejora también es posible (para usuarios que no son Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Ahorra espacio al hacer un iterador
ytpillai
13

Debido a PEP-0469 que renombró iteritems () a items () y PEP-3113 que eliminó el desempaquetado de parámetros Tuple , en Python 3.x debería escribir Martijn Pieters ♦ responda así:

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

Si bien mi respuesta original perdió el punto (al tratar de resolver este problema con la solución para acceder a la clave en la fábrica de defaultdict ), la modifiqué para proponer una solución real a la presente pregunta.

Aquí está:

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

Uso:

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

La idea es subclasificar el dict original para darle la funcionalidad deseada: "mapear" una función sobre todos los valores.

El punto a favor es que este diccionario se puede usar para almacenar los datos originales como si fuera un dict, mientras se transforma cualquier dato a pedido con una devolución de llamada.

Por supuesto, siéntase libre de nombrar la clase y la función de la manera que desee (el nombre elegido en esta respuesta está inspirado en la array_walk()función de PHP ).

Nota: Ni el bloque try- exceptni las returndeclaraciones son obligatorias para la funcionalidad, están ahí para imitar aún más el comportamiento de los PHP array_walk.

7heo.tk
fuente
1
Esto no resuelve la pregunta de OP, ya que el __missing__método no se llamará para las claves existentes, que queremos transformar, a menos que el método de fábrica aprobado utilice el dict de origen como una alternativa, pero como eso no es parte del uso del ejemplo, Considero que esta es una respuesta insatisfactoria al problema en cuestión.
Kaos
¿Qué claves existentes?
7heo.tk
A partir de la OP: Given a dictionary { k1: v1, k2: v2 ... } .... Es decir, ya tienes una dictpara empezar ...
Kaos
Me gustaría decir que ambos tenemos razón; pero creo que los dos estamos equivocados. Tienes razón en que mi respuesta no responde la pregunta; pero no por la razón que invocaste. Simplemente me perdí el punto, dando una forma de obtener {v1: f(v1), v2: f(v2), ...}dado [v1, v2, ...], y no un dictamen. Editaré mi respuesta para corregir eso.
7heo.tk
2

Para evitar la indexación desde el interior de lambda, como:

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

También puedes hacer:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
Atentamente
fuente
Esa es una manipulación inteligente dentro de la 2-tupla misma en el segundo ejemplo. Sin embargo, utiliza el desempaquetado automático de tuplas dentro de lambda, que ya no es compatible con Python 3. Por lambda(k,v)lo tanto , no funcionará. Ver stackoverflow.com/questions/21892989/…
Jonathan Komar
0

Acabo de encontrar este caso de uso. Implementé la respuesta de gens , agregando un enfoque recursivo para manejar valores que también son dictados:

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)

Esto puede ser útil cuando se trata de archivos json o yaml que codifican cadenas como bytes en Python 2

Oyono
fuente