Renombrar una clave de diccionario

401

¿Hay alguna forma de cambiar el nombre de una clave de diccionario, sin reasignar su valor a un nuevo nombre y eliminar la antigua clave de nombre; y sin iterar a través de la clave / valor dict?

En el caso de OrderedDict, haga lo mismo, manteniendo la posición de esa clave.

rabin utam
fuente
77
¿Qué quiere decir exactamente con "sin reasignar su valor a un nuevo nombre y eliminar la antigua clave de nombre"? tal como lo veo, esa es la definición de cambiar el nombre de una clave, y todas las respuestas a continuación reasignan el valor y eliminan el antiguo nombre de la clave. aún no ha aceptado una respuesta, ¿entonces tal vez estos no han logrado lo que busca?
dbliss
55
Realmente necesita especificar los números de versión. A partir de Python 3.7, la especificación del lenguaje ahora garantiza que los dictados sigan el orden de inserción . Eso también hace que OrderedDict sea en su mayoría obsoleto (a menos que a) desee un código que también sea compatible con 2.xo 3.6- o b) le interesen los problemas enumerados en ¿OrderedDict se volverá redundante en Python 3.7? ) Y en 3.6, el orden de inserción del diccionario estaba garantizado por la implementación de CPython (pero no por la especificación del idioma).
smci
1
@smci En Python 3.7 los dictados siguen el orden de inserción, sin embargo, todavía son diferentes de OrderedDict. los dictados ignoran el orden cuando se comparan por igualdad, mientras que OrderedDicttienen en cuenta el orden cuando se comparan. Sé que se vinculó a algo que lo explica, pero pensé que su comentario podría haber engañado a quienes no lo hayan leído.
Flimm
@Flimm: eso es cierto, pero no puedo ver que importe, esta pregunta hace dos preguntas en una, y la intención de la segunda parte ha sido reemplazada. "los dictados ignoran el orden cuando se los compara por igualdad" Sí, porque se supone que no deben comparar por orden. "mientras que OrderedDicttoma en cuenta el orden cuando se compara" Ok, pero a nadie le importa después de 3.7. Afirmo que OrderedDictes en gran parte obsoleto, ¿puede articular razones por las que no lo es? por ejemplo, aquí no es persuasivo a menos que lo necesitereversed()
smci
@Flimm: No puedo encontrar ningún argumento creíble por qué OrderedDict no es obsoleto en el código en 3.7+ a menos que tenga que ser compatible con pre-3.7 o 2.x. Entonces, por ejemplo, esto no es del todo persuasivo. En particular, "el uso de un OrderedDict comunica su intención ..." es un argumento terrible para la deuda técnica y la ofuscación completamente necesarias. La gente simplemente debería volver a dictar y seguir adelante. Así de simple
smci

Respuestas:

713

Para un dict regular, puede usar:

mydict[new_key] = mydict.pop(old_key)

Para un OrderedDict, creo que debes construir uno completamente nuevo usando una comprensión.

>>> OrderedDict(zip('123', 'abc'))
OrderedDict([('1', 'a'), ('2', 'b'), ('3', 'c')])
>>> oldkey, newkey = '2', 'potato'
>>> OrderedDict((newkey if k == oldkey else k, v) for k, v in _.viewitems())
OrderedDict([('1', 'a'), ('potato', 'b'), ('3', 'c')])

La modificación de la clave en sí, como parece ser esta pregunta, no es práctica porque las claves dict son generalmente objetos inmutables como números, cadenas o tuplas. En lugar de intentar modificar la clave, reasignar el valor a una nueva clave y eliminar la clave anterior es cómo puede lograr el "cambio de nombre" en python.

wim
fuente
Para python 3.5, creo que dict.popes viable para un OrderedDict basado en mi prueba.
Daniel
55
No, OrderedDictconservará el orden de inserción, que no es de lo que se hizo la pregunta.
wim
64

mejor método en 1 línea:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}
Tcll
fuente
3
lo que si usted tiene d = {('test', 'foo'):[0,1,2]}?
Petr Fedosov
44
@PetrFedosov, entonces lo hacesd[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer
17
Esta respuesta llegó dos años después de la respuesta de wim, lo que sugiere exactamente lo mismo, sin información adicional. ¿Qué me estoy perdiendo?
Andras Deak
@AndrasDeak la diferencia entre un dict () y un OrderedDict ()
Tcll
44
La respuesta de wim en su revisión inicial de 2013 , solo ha habido adiciones desde entonces. El orden solo proviene del criterio de OP.
Andras Deak
29

Usando un cheque para newkey!=oldkey, de esta manera puede hacer:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]
Srinivas Reddy Thatiparthy
fuente
44
esto funciona muy bien, pero no mantiene el orden original porque la nueva clave se agrega al final de forma predeterminada (en python3).
szeitlin 01 de
2
@szeitlin no debe confiar en el dictorden, incluso si python3.6 + lo inicializa en forma ordenada, las versiones anteriores no lo hacen y no es realmente una característica, solo un efecto secundario de cómo se modificaron los dictados de py3.6 +. Úselo OrderedDictsi le importa el orden.
Granitosaurus el
2
Gracias @Granitosaurus, no necesitaba que me lo explicaras. Ese no fue el punto de mi comentario.
szeitlin
55
@szeitlin su comentario a entender que el hecho de que cambia el orden importa dict de alguna manera, forma o forma, cuando en realidad nadie debe confiar en el diccionario de orden por lo que este hecho es completamente irrelevante
Granitosaurus
11

En caso de renombrar todas las teclas del diccionario:

target_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
new_keys = ['k4','k5','k6']

for key,n_key in zip(target_dict.keys(), new_keys):
    target_dict[n_key] = target_dict.pop(key)
ikbel benabdessamad
fuente
1
¿Se target_dict.keys()garantiza que tenga el mismo orden que en la definición? Pensé que un dict de Python no está ordenado, y el orden desde la vista de claves de un dict es impredecible
ttimasdf
Creo que deberías ordenar tus llaves en algún momento ...
Espoir Murhabazi
10

Puede usar esto OrderedDict recipeescrito por Raymond Hettinger y modificarlo para agregar un renamemétodo, pero esto será una O (N) en complejidad:

def rename(self,key,new_key):
    ind = self._keys.index(key)  #get the index of old key, O(N) operation
    self._keys[ind] = new_key    #replace old key with new key in self._keys
    self[new_key] = self[key]    #add the new key, this is added at the end of self._keys
    self._keys.pop(-1)           #pop the last item in self._keys

Ejemplo:

dic = OrderedDict((("a",1),("b",2),("c",3)))
print dic
dic.rename("a","foo")
dic.rename("b","bar")
dic["d"] = 5
dic.rename("d","spam")
for k,v in  dic.items():
    print k,v

salida:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
Ashwini Chaudhary
fuente
1
@MERose Agregue el archivo Python en algún lugar de la ruta de búsqueda de su módulo e impórtelo OrderedDict. Pero recomendaría: Crear una clase que herede OrderedDicty agregarle un renamemétodo.
Ashwini Chaudhary
Parece que desde entonces OrderedDict ha sido reescrito para usar una lista doblemente vinculada, por lo que probablemente todavía haya una manera de hacerlo, pero requiere muchas más acrobacias. : - /
szeitlin 01 de
6

Algunas personas antes que yo mencionaron el .poptruco para eliminar y crear una clave en una sola línea.

Personalmente encuentro que la implementación más explícita es más legible:

d = {'a': 1, 'b': 2}
v = d['b']
del d['b']
d['c'] = v

El código anterior devuelve {'a': 1, 'c': 2}

Uri Goren
fuente
1

Otras respuestas son bastante buenas, pero en python3.6, el dict regular también tiene orden. Por lo tanto, es difícil mantener la posición de la llave en el caso normal.

def rename(old_dict,old_name,new_name):
    new_dict = {}
    for key,value in zip(old_dict.keys(),old_dict.values()):
        new_key = key if key != old_name else new_name
        new_dict[new_key] = old_dict[key]
    return new_dict
helloswift123
fuente
1

En Python 3.6 (¿en adelante?) Iría por el siguiente one-liner

test = {'a': 1, 'old': 2, 'c': 3}
old_k = 'old'
new_k = 'new'
new_v = 4  # optional

print(dict((new_k, new_v) if k == old_k else (k, v) for k, v in test.items()))

que produce

{'a': 1, 'new': 4, 'c': 3}

Vale la pena señalar que sin la printdeclaración la consola ipython / notebook jupyter presenta el diccionario en el orden que elijan ...

KeithWM
fuente
0

Se me ocurrió esta función que no muta el diccionario original. Esta función también es compatible con la lista de diccionarios.

import functools
from typing import Union, Dict, List


def rename_dict_keys(
    data: Union[Dict, List[Dict]], old_key: str, new_key: str
):
    """
    This function renames dictionary keys

    :param data:
    :param old_key:
    :param new_key:
    :return: Union[Dict, List[Dict]]
    """
    if isinstance(data, dict):
        res = {k: v for k, v in data.items() if k != old_key}
        try:
            res[new_key] = data[old_key]
        except KeyError:
            raise KeyError(
                "cannot rename key as old key '%s' is not present in data"
                % old_key
            )
        return res
    elif isinstance(data, list):
        return list(
            map(
                functools.partial(
                    rename_dict_keys, old_key=old_key, new_key=new_key
                ),
                data,
            )
        )
    raise ValueError("expected type List[Dict] or Dict got '%s' for data" % type(data))
Lokesh Sanapalli
fuente
-1

Estoy usando la respuesta de @wim anterior, con dict.pop () al cambiar el nombre de las teclas, pero encontré un problema. Al recorrer el dict para cambiar las claves, sin separar la lista de claves antiguas por completo de la instancia del dict, resultó en el ciclo de claves nuevas, cambiadas en el bucle y se perdieron algunas claves existentes.

Para empezar, lo hice de esta manera:

for current_key in my_dict:
    new_key = current_key.replace(':','_')
    fixed_metadata[new_key] = fixed_metadata.pop(current_key)

Descubrí que al recorrer el dict de esta manera, el diccionario seguía encontrando claves incluso cuando no debería, es decir, ¡las nuevas claves, las que había cambiado! Necesitaba separar las instancias completamente entre sí para (a) evitar encontrar mis propias claves cambiadas en el ciclo for, y (b) encontrar algunas claves que no se encontraban dentro del ciclo por alguna razón.

Estoy haciendo esto ahora:

current_keys = list(my_dict.keys())
for current_key in current_keys:
    and so on...

Fue necesario convertir my_dict.keys () en una lista para liberarse de la referencia al cambio dict. Solo usando my_dict.keys () me mantuvo atado a la instancia original, con los extraños efectos secundarios.

excyberlabber
fuente
-1

En caso de que alguien quiera cambiar el nombre de todas las claves a la vez proporcionando una lista con los nuevos nombres:

def rename_keys(dict_, new_keys):
    """
     new_keys: type List(), must match length of dict_
    """

    # dict_ = {oldK: value}
    # d1={oldK:newK,} maps old keys to the new ones:  
    d1 = dict( zip( list(dict_.keys()), new_keys) )

          # d1{oldK} == new_key 
    return {d1[oldK]: value for oldK, value in dict_.items()}
Sirmione
fuente
-1

@ helloswift123 Me gusta tu función. Aquí hay una modificación para cambiar el nombre de varias claves en una sola llamada:

def rename(d, keymap):
    """
    :param d: old dict
    :type d: dict
    :param keymap: [{:keys from-keys :values to-keys} keymap]
    :returns: new dict
    :rtype: dict
    """
    new_dict = {}
    for key, value in zip(d.keys(), d.values()):
        new_key = keymap.get(key, key)
        new_dict[new_key] = d[key]
    return new_dict
skullgoblet1089
fuente
-2

Suponga que desea cambiar el nombre de la clave k3 a k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Shishir
fuente
1
No funciona, ¿quisiste decir ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR
Esto devolverá un TypeError: 'dict' object is not callableSi con temp_dict('k3')usted está tratando de hacer referencia al valor de la k3clave, entonces debe usar corchetes y no paréntesis. Sin embargo, esto solo agregará una nueva clave al diccionario y no cambiará el nombre de una clave existente, como se solicitó.
Jasmine