Renommer une clé de dictionnaire

401

Existe-t-il un moyen de renommer une clé de dictionnaire sans réaffecter sa valeur à un nouveau nom et supprimer l'ancienne clé de nom; et sans itérer via la clé / valeur dict?

En cas de OrderedDict, faites de même, tout en conservant la position de cette clé.

rabin utam
la source
7
qu'entendez-vous exactement par «sans réaffecter sa valeur à un nouveau nom et en supprimant l'ancienne clé de nom»? la façon dont je le vois, c'est la définition de renommer une clé, et toutes les réponses ci-dessous réaffectent la valeur et suppriment l'ancien nom de la clé. vous devez encore accepter une réponse, alors peut-être que ceux-ci n'ont pas accompli ce que vous cherchez?
dbliss
5
Vous devez vraiment spécifier le (s) numéro (s) de version. Depuis Python 3.7, la spécification de langue garantit désormais que les dicts suivent l'ordre d'insertion . Cela rend également OrderedDict principalement obsolète (sauf si a) vous voulez du code qui rétroporte également vers 2.x ou 3.6- ou b) vous vous souciez des problèmes répertoriés dans OrderedDict deviendra-t-il redondant dans Python 3.7? ). Et en 3.6, l'ordre d'insertion du dictionnaire était garanti par l'implémentation de CPython (mais pas la spécification de langue).
smci
1
@smci En Python 3.7, les dictés suivent l'ordre d'insertion, mais ils sont toujours différents de OrderedDict. les dits ignorent l'ordre lorsqu'ils sont comparés pour l'égalité, alors qu'ils OrderedDicttiennent compte de l'ordre lorsqu'ils sont comparés. Je sais que vous avez lié à quelque chose qui l'explique, mais j'ai pensé que votre commentaire pourrait avoir induit en erreur ceux qui n'avaient peut-être pas lu ce lien.
Flimm
@Flimm: c'est vrai mais je ne vois pas que ça compte, cette question pose deux questions en une, et l'intention de la deuxième partie a été remplacée. "les dict ignorent l'ordre quand ils sont comparés pour l'égalité" Oui parce qu'ils ne sont pas censés comparer par ordre. "alors que l' OrderedDictordre prend en compte lors de la comparaison" Ok mais personne ne s'en soucie après la 3.7. Je prétends qu'il OrderedDictest en grande partie obsolète, pouvez-vous expliquer pourquoi il ne l'est pas? par exemple ici n'est pas convaincant à moins que vous ayez besoinreversed()
smci
@Flimm: Je ne trouve aucun argument crédible expliquant pourquoi OrderedDict n'est pas obsolète sur le code en 3.7+, sauf s'il doit être compatible avec les versions antérieures à 3.7 ou 2.x. Donc, par exemple, ce n'est pas du tout convaincant. En particulier, "l'utilisation d'un OrderedDict communique votre intention ..." est un argument épouvantable pour une dette technique et une obfuscation complètement nécessaires. Les gens devraient simplement revenir à la dictée et continuer. C'est simple.
smci

Réponses:

713

Pour un dict régulier, vous pouvez utiliser:

mydict[new_key] = mydict.pop(old_key)

Pour un OrderedDict, je pense que vous devez en construire un entièrement nouveau en utilisant une compréhension.

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

Modifier la clé elle-même, comme cette question semble se poser, n'est pas pratique car les clés dict sont généralement des objets immuables tels que des nombres, des chaînes ou des tuples. Au lieu d'essayer de modifier la clé, réaffecter la valeur à une nouvelle clé et supprimer l'ancienne clé est de savoir comment vous pouvez obtenir le "renommer" en python.

wim
la source
Pour python 3.5, je pense que dict.popc'est réalisable pour un OrderedDict basé sur mon test.
Daniel
5
Non, OrderedDictpréservera l'ordre d'insertion, ce qui n'est pas ce sur quoi porte la question.
wim
64

meilleure méthode en 1 ligne:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}
Tcll
la source
3
Et si vous en avez d = {('test', 'foo'):[0,1,2]}?
Petr Fedosov
4
@PetrFedosov, alors vous faitesd[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer
17
Cette réponse est venue deux ans après la réponse de wim qui suggère exactement la même chose, sans aucune information supplémentaire. Qu'est-ce que je rate?
Andras Deak
@AndrasDeak la différence entre un dict () et un OrderedDict ()
Tcll
4
réponse de wim dans sa révision initiale de 2013 , il n'y a eu que des ajouts depuis. L'ordre ne vient que du critère d'OP.
Andras Deak
29

En utilisant un chèque pour newkey!=oldkey, vous pouvez ainsi:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]
Srinivas Reddy Thatiparthy
la source
4
cela fonctionne à merveille, mais ne conserve pas l'ordre d'origine car la nouvelle clé est ajoutée à la fin par défaut (en python3).
szeitlin
2
@szeitlin vous ne devriez pas vous fier à l' dictordre, même si python3.6 + l'initialise sous forme ordonnée, les versions précédentes ne le font pas et ce n'est pas vraiment une fonctionnalité juste un effet secondaire de la façon dont les dits py3.6 + ont été modifiés. Utilisez OrderedDictsi vous vous souciez de la commande.
Granitosaurus
2
Merci @Granitosaurus, je n'avais pas besoin que vous m'expliquiez cela. Ce n'était pas l'objet de mon commentaire.
szeitlin du
5
@szeitlin votre commentaire impliquait que le fait qu'il modifie l'ordre du dict importe d'une manière ou d'une autre alors qu'en réalité personne ne devrait se fier à l'ordre du dictionnaire, ce fait est donc totalement hors de propos
Granitosaurus
11

En cas de renommage de toutes les clés du dictionnaire:

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
la source
1
La target_dict.keys()garantie est-elle du même ordre que dans la définition? Je pensais qu'un dict Python n'est pas ordonné, et l'ordre de la vue des clés d'un dict est imprévisible
ttimasdf
Je pense que vous devriez trier vos clés à un moment donné ...
Espoir Murhabazi
10

Vous pouvez utiliser ceci OrderedDict recipeécrit par Raymond Hettinger et le modifier pour ajouter une renameméthode, mais cela va être un O (N) en complexité:

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

Exemple:

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

production:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
Ashwini Chaudhary
la source
1
@MERose Ajoutez le fichier Python quelque part dans le chemin de recherche de votre module et importez- OrderedDictle. Mais je recommanderais: Créez une classe qui hérite de OrderedDictet ajoutez-y une renameméthode.
Ashwini Chaudhary
On dirait que OrderedDict a depuis été réécrit pour utiliser une liste doublement liée, donc il y a probablement encore un moyen de le faire, mais cela nécessite beaucoup plus d'acrobaties. : - /
szeitlin
6

Quelques personnes avant moi ont mentionné l' .popastuce pour supprimer et créer une clé dans une ligne.

Personnellement, je trouve la mise en œuvre plus explicite plus lisible:

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

Le code ci-dessus renvoie {'a': 1, 'c': 2}

Uri Goren
la source
1

Les autres réponses sont plutôt bonnes, mais en python3.6, le dict régulier a également de l'ordre. Il est donc difficile de conserver la position de la clé dans un cas 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
la source
1

En Python 3.6 (à partir de maintenant?), Je choisirais le one-liner suivant

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

qui produit

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

Il peut être intéressant de noter que sans la printdéclaration, le bloc-notes console / jupyter ipython présente le dictionnaire dans l'ordre de leur choix ...

KeithWM
la source
0

J'ai trouvé cette fonction qui ne mute pas le dictionnaire d'origine. Cette fonction prend également en charge la liste des dictionnaires.

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
la source
-1

J'utilise la réponse de @wim ci-dessus, avec dict.pop () lors du changement de nom des clés, mais j'ai trouvé un problème. Faire défiler le dict pour changer les clés, sans séparer complètement la liste des anciennes clés de l'instance de dict, a entraîné le cyclage de nouvelles clés modifiées dans la boucle et la disparition de certaines clés existantes.

Pour commencer, je l'ai fait de cette façon:

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

J'ai trouvé qu'en parcourant le dict de cette manière, le dictionnaire continuait à trouver des clés même quand ce n'était pas le cas, c'est-à-dire les nouvelles clés, celles que j'avais changées! Je devais séparer complètement les instances les unes des autres pour (a) éviter de trouver mes propres clés modifiées dans la boucle for, et (b) trouver des clés qui n'étaient pas trouvées dans la boucle pour une raison quelconque.

Je fais ça maintenant:

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

La conversion de my_dict.keys () en une liste était nécessaire pour se libérer de la référence au dict changeant. Le simple fait d'utiliser my_dict.keys () m'a gardé lié à l'instance d'origine, avec les effets secondaires étranges.

excyberlabber
la source
-1

Dans le cas où quelqu'un souhaite renommer toutes les clés à la fois en fournissant une liste avec les nouveaux noms:

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
la source
-1

@ helloswift123 J'aime votre fonction. Voici une modification pour renommer plusieurs clés en un seul appel:

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
la source
-2

Supposons que vous souhaitiez renommer la clé k3 en k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Shishir
la source
1
Ne fonctionne pas, vouliez-vous dire ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR
Cela renverra un TypeError: 'dict' object is not callableSi avec temp_dict('k3')vous essayez de référencer la valeur de la k3clé, vous devez utiliser des crochets et non des parenthèses. Cependant, cela ajoutera simplement une nouvelle clé au dictionnaire et ne renommera pas une clé existante, comme demandé.
Jasmine