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.


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.


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]}

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]

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)

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

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}


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

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 ...


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))

-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.


-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()}

-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

-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')

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.