Rinomina una chiave del dizionario


401

C'è un modo per rinominare una chiave del dizionario, senza riassegnare il suo valore a un nuovo nome e rimuovere la vecchia chiave del nome; e senza iterare tramite chiave / valore dict?

Nel caso di OrderedDict, fai lo stesso, mantenendo la posizione di quella chiave.


7
cosa intendi esattamente con "senza riassegnare il suo valore a un nuovo nome e rimuovere la vecchia chiave"? per come la vedo io, questa è la definizione di ridenominazione di una chiave e tutte le risposte di seguito riassegnano il valore e rimuovono il vecchio nome della chiave. devi ancora accettare una risposta, quindi forse questi non hanno realizzato ciò che stai cercando?
dbliss,

5
Devi davvero specificare i numeri di versione. A partire da Python 3.7, le specifiche del linguaggio ora garantiscono che i caratteri seguono l'ordine di inserimento . Ciò rende anche OrderedDict per lo più obsoleto (a meno che a) non desideri un codice che esegua anche il backport su 2.xo 3.6- o b) e ti preoccupi dei problemi elencati in OrderedDict diventerà ridondante in Python 3.7? ). E nel 3.6, l'ordine di inserimento del dizionario era garantito dall'implementazione di CPython (ma non dalle specifiche della lingua).
smci,

1
@smci In Python 3.7 i dicts seguono l'ordine di inserimento, tuttavia sono ancora diversi da OrderedDict. i dicts ignorano l'ordine quando vengono confrontati per l'uguaglianza, mentre OrderedDicttengono conto dell'ordine quando vengono confrontati. So che ti sei collegato a qualcosa che lo spiega, ma ho pensato che il tuo commento avrebbe potuto ingannare coloro che potrebbero non aver letto quel link.
Flimm,

@Flimm: è vero ma non riesco a capire che sia importante, questa domanda pone due domande in una e l'intento della seconda parte è stato superato. "i dicts ignorano l'ordine quando vengono confrontati per uguaglianza" Sì, perché non dovrebbero comparare per ordine. "considerando che OrderedDicttiene conto del confronto" Ok ma a nessuno importa dopo il 3.7. Dico che OrderedDictè in gran parte obsoleto, puoi articolare i motivi per cui non lo è? ad esempio, qui non è persuasivo a meno che non sia necessarioreversed()
smci

@Flimm: non riesco a trovare argomenti credibili per cui OrderedDict non è obsoleto sul codice in 3.7+ a meno che non debba essere compatibile con pre-3.7 o 2.x. Quindi, ad esempio, questo non è affatto persuasivo. In particolare "l'utilizzo di un OrderedDict comunica la tua intenzione ..." è un argomento spaventoso per il debito tecnico e l'offuscamento completamente necessari. Le persone dovrebbero semplicemente tornare a dettare e andare avanti. Così semplice.
smci

Risposte:


712

Per un regolare dict, è possibile utilizzare:

mydict[new_key] = mydict.pop(old_key)

Per un OrderedDict, penso che tu debba costruirne uno completamente nuovo usando una comprensione.

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

Modificare la chiave stessa, come sembra porsi questa domanda, non è pratico perché le chiavi dict sono di solito oggetti immutabili come numeri, stringhe o tuple. Invece di provare a modificare la chiave, riassegnare il valore a una nuova chiave e rimuovere la vecchia chiave è come è possibile ottenere la "rinomina" in Python.


Per python 3.5, penso che dict.popsia realizzabile per un OrderedDict basato sul mio test.
Daniel,

5
No, OrderedDictconserverà l'ordine di inserimento, che non è la domanda posta.
mercoledì

64

metodo migliore in 1 riga:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}

3
cosa succede se hai d = {('test', 'foo'):[0,1,2]}?
Petr Fedosov,

4
@PetrFedosov, allora lo faid[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer,

17
Questa risposta è arrivata due anni dopo la risposta di Wim che suggerisce la stessa identica cosa, senza ulteriori informazioni. Cosa mi sto perdendo?
Andras Deak,

@AndrasDeak la differenza tra un dict () e un OrderedDict ()
Fino al

4
La risposta di WIM nella sua revisione iniziale del 2013 , da allora ci sono state solo aggiunte. L'ordinamento deriva solo dal criterio di OP.
Andras Deak,

29

Usando un controllo per newkey!=oldkey, in questo modo puoi fare:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]

4
funziona magnificamente, ma non mantiene l'ordine originale perché la nuova chiave viene aggiunta alla fine per impostazione predefinita (in python3).
szeitlin,

2
@szeitlin non dovresti fare affidamento dictsull'ordine, anche se python3.6 + lo inizializza in forma ordinata, le versioni precedenti no e non è in realtà una caratteristica solo un ripensamento di come py3.6 + dicts sono cambiati. Utilizzare OrderedDictse ti interessa l'ordine.
Granitosaurus,

2
Grazie @Granitosaurus, non avevo bisogno che tu me lo spiegassi. Non era questo il punto del mio commento.
szeitlin,

5
@szeitlin il tuo commento implicava che il fatto che cambia l'ordine del dict importa in qualche modo, forma o forma quando in realtà nessuno dovrebbe fare affidamento sull'ordine del dizionario, quindi questo fatto è completamente irrilevante
Granitosaurus

11

In caso di rinominare tutte le chiavi del dizionario:

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
È target_dict.keys()garantito lo stesso ordine della definizione? Pensavo che un dict di Python non fosse ordinato e l'ordine dal punto di vista delle chiavi di un dict fosse imprevedibile
ttimasdf

Penso che dovresti ordinare le tue chiavi ad un certo punto ...
Espoir Murhabazi,

10

Puoi usare questo OrderedDict recipescritto da Raymond Hettinger e modificarlo per aggiungere un renamemetodo, ma sarà una O (N) in complessità:

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

Esempio:

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

produzione:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5

1
@MERose Aggiungi il file Python da qualche parte nel percorso di ricerca del tuo modulo e importa OrderedDictda esso. Ma consiglierei: creare una classe che eredita OrderedDicte aggiungere un renamemetodo ad essa.
Ashwini Chaudhary,

Sembra che OrderedDict sia stato riscritto per usare un elenco doppiamente collegato, quindi probabilmente c'è ancora un modo per farlo, ma richiede molte più acrobazie. : - /
szeitlin,

6

Alcune persone prima di me hanno menzionato il .poptrucco per eliminare e creare una chiave in una riga.

Personalmente trovo più leggibile l'implementazione più esplicita:

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

Il codice sopra riportato ritorna {'a': 1, 'c': 2}


1

Altre risposte sono piuttosto buone, ma in python3.6 anche il dict regolare ha un ordine. Quindi è difficile mantenere la posizione della chiave nel caso normale.

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

In Python 3.6 (in poi?) Sceglierei il seguente 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()))

che produce

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

Vale la pena notare che senza l' printaffermazione la console ipython / notebook jupyter presentano il dizionario in un ordine di loro scelta ...


0

Mi è venuta in mente questa funzione che non muta il dizionario originale. Questa funzione supporta anche l'elenco dei dizionari.

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

Sto usando la risposta di @wim sopra, con dict.pop () quando ho rinominato le chiavi, ma ho trovato un gotcha. Scorrendo il dict per cambiare le chiavi, senza separare completamente l'elenco delle vecchie chiavi dall'istanza di dict, si è verificato il ciclaggio di nuove chiavi, le chiavi sono state cambiate nel loop e mancano alcune chiavi esistenti.

Per cominciare, l'ho fatto in questo modo:

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

Ho scoperto che in questo modo il dizionario continuava a cercare le chiavi anche quando non avrebbe dovuto, cioè le nuove chiavi, quelle che avevo cambiato! Avevo bisogno di separare completamente le istanze l'una dall'altra per (a) evitare di trovare le mie chiavi modificate nel ciclo for, e (b) trovare alcune chiavi che non erano state trovate nel ciclo per qualche motivo.

Lo sto facendo ora:

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

La conversione di my_dict.keys () in un elenco era necessaria per liberarsi del riferimento al dict in evoluzione. Il solo utilizzo di my_dict.keys () mi ha tenuto legato all'istanza originale, con gli strani effetti collaterali.


-1

Nel caso in cui qualcuno desideri rinominare tutte le chiavi contemporaneamente fornendo un elenco con i nuovi nomi:

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 Mi piace la tua funzione. Ecco una modifica per rinominare più chiavi in ​​una singola chiamata:

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

Supponiamo di voler rinominare la chiave da k3 a k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')

1
Non funziona, intendevi ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR,

Questo restituirà un TypeError: 'dict' object is not callableSe con temp_dict('k3')te stai cercando di fare riferimento al valore della k3chiave, allora dovresti usare parentesi quadre e non parentesi. Tuttavia, ciò aggiungerà semplicemente una nuova chiave al dizionario e non rinominerà una chiave esistente, come richiesto.
Jasmine,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.