Renomear uma chave de dicionário

401

Existe uma maneira de renomear uma chave de dicionário, sem reatribuir seu valor a um novo nome e remover a chave de nome antiga; e sem iterar através da chave / valor do dict?

No caso de OrderedDict, faça o mesmo, mantendo a posição dessa chave.

rabin utam
fonte
7
o que exatamente você quer dizer com "sem reatribuir seu valor a um novo nome e remover a chave de nome antiga"? do jeito que eu vejo, essa é a definição de renomear uma chave, e todas as respostas abaixo reatribuem o valor e removem o nome da chave antiga. você ainda tem que aceitar uma resposta, então talvez elas não tenham conseguido o que você está procurando?
Dbliss 06/04
5
Você realmente precisa especificar o número da versão. A partir do Python 3.7, a especificação da linguagem agora garante que os dictos seguem a ordem de inserção . Isso também torna o OrderedDict praticamente obsoleto (a menos que a) você queira um código que também faça backports para 2.x ou 3.6- ou b) você se preocupe com os problemas listados em Will OrderedDict se tornará redundante no Python 3.7? ) E na versão 3.6, a ordem de inserção do dicionário foi garantida pela implementação do CPython (mas não pela especificação do idioma).
smci 4/03/19
11
@smci No Python 3.7 os ditados seguem a ordem de inserção, no entanto, ainda são diferentes de OrderedDict. os decretos ignoram a ordem quando estão sendo comparados em relação à igualdade, enquanto OrderedDictlevam em consideração a ordem ao serem comparados. Sei que você vinculou a algo que explica isso, mas achei que seu comentário poderia ter enganado aqueles que talvez não tenham lido esse link.
Flimm
@ Limlim: isso é verdade, mas não vejo como isso importa, essa pergunta faz duas perguntas em uma, e a intenção da segunda parte foi substituída. "ditados ignoram a ordem quando estão sendo comparados pela igualdade" Sim, porque eles não devem ser comparados por ordem. "Considerando que OrderedDictleva em conta a ordem ao ser comparado" Ok, mas ninguém se importa após o 3.7. Eu afirmo que OrderedDicté amplamente obsoleto, você pode articular razões pelas quais não é? por exemplo, aqui não é convincente, a menos que você precisareversed()
SMCI
@Flimm: Não consigo encontrar argumentos credíveis para que o OrderedDict não seja obsoleto no código no 3.7+, a menos que seja compatível com o pré-3.7 ou 2.x. Então, por exemplo, isso não é nada convincente. Em particular, "o uso de um OrderedDict comunica sua intenção ..." é um argumento apavorante para dívida técnica e ofuscação completamente necessárias. As pessoas devem simplesmente voltar a ditar e seguir em frente. Que simples.
SMCI

Respostas:

712

Para um ditado regular, você pode usar:

mydict[new_key] = mydict.pop(old_key)

Para um OrderedDict, acho que você deve criar um inteiramente novo usando uma compreensão.

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

Modificar a chave em si, como esta pergunta parece estar se perguntando, é impraticável porque as chaves de ditado geralmente são objetos imutáveis , como números, strings ou tuplas. Em vez de tentar modificar a chave, reatribuir o valor a uma nova chave e remover a chave antiga é como você pode obter a "renomeação" em python.

wim
fonte
Para python 3.5, acho que dict.popé viável para um OrderedDict com base no meu teste.
Daniel
5
Não, OrderedDictpreservará o pedido de inserção, que não é sobre o que a pergunta foi feita.
Wim
64

melhor método em 1 linha:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}
Tcll
fonte
3
e se você tiver d = {('test', 'foo'):[0,1,2]}?
Petr Fedosov
4
@PetrFedosov, então você fazd[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer
17
Essa resposta veio dois anos após a resposta de wim, que sugere exatamente a mesma coisa, sem informações adicionais. o que estou perdendo?
Andras Deak
@AndrasDeak a diferença entre um dicionário () e um OrderedDict ()
Tcll
4
resposta de wim em sua revisão inicial de 2013 , houve apenas acréscimos desde então. A ordem só vem do critério do OP.
Andras Deak
29

Usando uma verificação para newkey!=oldkey, desta maneira, você pode fazer:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]
Srinivas Reddy Thatiparthy
fonte
4
isso funciona lindamente, mas não mantém a ordem original porque a nova chave é adicionada no final por padrão (em python3).
Szeitlin 01/10/19
2
@szeitlin, você não deve confiar na dictordem, mesmo que o python3.6 + o inicialize na forma ordenada, as versões anteriores não o fazem e não é realmente um recurso apenas um efeito posterior de como os dicionários py3.6 + foram alterados. Use OrderedDictse você se importa com a ordem.
Granitosaurus
2
Obrigado @Granitosaurus, eu não precisava que você me explicasse isso. Esse não foi o ponto do meu comentário.
Szeitlin 15/11
5
@szeitlin seu comentário implícito que o fato de que ele muda as questões de ordem dict de alguma maneira, forma ou formulário, quando na realidade, ninguém deve confiar em dicionário de ordem para este fato é completamente irrelevante
Granitosaurus
11

No caso de renomear todas as chaves do dicionário:

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
fonte
11
A target_dict.keys()garantia é da mesma ordem que na definição? Eu pensei que um ditado de Python não era ordenado, e a ordem da exibição das chaves de um
ditado
Eu acho que você deve classificar suas chaves em algum momento ...
Espoir Murhabazi
10

Você pode usar isso OrderedDict recipeescrito por Raymond Hettinger e modificá-lo para adicionar um renamemétodo, mas isso será um O (N) em complexidade:

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

Exemplo:

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

resultado:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
Ashwini Chaudhary
fonte
11
@MERose Adicione o arquivo Python em algum lugar no caminho de pesquisa do módulo e importe OrderedDict-o. Mas eu recomendaria: Crie uma classe que herda OrderedDicte adicione um renamemétodo a ela.
Ashwini Chaudhary
Parece que o OrderedDict foi reescrito para usar uma lista duplamente vinculada; portanto, provavelmente ainda há uma maneira de fazer isso, mas requer muito mais acrobacias. : - /
szeitlin
6

Algumas pessoas antes de mim mencionaram o .poptruque para excluir e criar uma chave em uma linha.

Pessoalmente, acho a implementação mais explícita mais legível:

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

O código acima retorna {'a': 1, 'c': 2}

Uri Goren
fonte
1

Outras respostas são muito boas, mas no python3.6, o dict regular também tem ordem. Portanto, é difícil manter a posição da chave no 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
fonte
1

No Python 3.6 (em diante?), Eu iria para o seguinte 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 produz

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

Pode-se notar que, sem a printdeclaração, o notebook ipython console / jupyter apresenta o dicionário em uma ordem de sua escolha ...

KeithWM
fonte
0

Eu vim com essa função que não altera o dicionário original. Esta função também suporta lista de dicionários também.

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
fonte
-1

Estou usando a resposta de @wim acima, com dict.pop () ao renomear chaves, mas encontrei uma pegadinha. Percorrer o dict para alterar as chaves, sem separar completamente a lista de chaves antigas da instância dict, resultou em um ciclo novo, alterou as chaves no loop e faltou algumas chaves existentes.

Para começar, eu fiz desta maneira:

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

Eu descobri que, percorrendo o ditado dessa maneira, o dicionário continuava encontrando chaves, mesmo quando não deveria, ou seja, as novas chaves, as que eu havia mudado! Eu precisava separar completamente as instâncias umas das outras para (a) evitar encontrar minhas próprias chaves alteradas no loop for e (b) encontrar algumas chaves que não estavam sendo encontradas dentro do loop por algum motivo.

Estou fazendo isso agora:

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

A conversão de my_dict.keys () em uma lista foi necessária para se livrar da referência ao dict em mudança. Apenas o uso de my_dict.keys () me manteve ligado à instância original, com os estranhos efeitos colaterais.

excyberlabber
fonte
-1

Caso alguém queira renomear todas as chaves de uma só vez, forneça uma lista com os novos nomes:

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
fonte
-1

@ helloswift123 Gosto da sua função. Aqui está uma modificação para renomear várias chaves em uma única chamada:

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
fonte
-2

Suponha que você queira renomear a chave k3 para k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Shishir
fonte
11
Não funciona, você quis dizer ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR 04/09/19
Isso retornará um if TypeError: 'dict' object is not callableSe temp_dict('k3')você estiver tentando fazer referência ao valor da k3chave, use colchetes e não parênteses. No entanto, isso adicionará apenas uma nova chave ao dicionário e não renomeará uma chave existente, conforme solicitado.
Jasmine