Переименовать словарный ключ

401

Есть ли способ переименовать ключ словаря, не переназначая его значение на новое имя и удаляя старый ключ имени; и без перебора по ключу / значению dict?

В случае OrderedDict, сделайте то же самое, сохраняя позицию этого ключа.

Рабин Утам
источник
7
что именно вы подразумеваете под "без переназначения его значения новому имени и удаления старого ключа имени"? как я понимаю, это определение переименования ключа, и все ответы ниже переназначают значение и удаляют старое имя ключа. Вы еще не приняли ответ, так что, возможно, они не достигли того, что вы ищете?
dbliss
5
Вам действительно нужно указать номер версии. Начиная с Python 3.7, спецификация языка теперь гарантирует, что дикты следуют порядку вставки . Это также делает OrderedDict в основном устаревшим (если только а) вам не нужен код, который также переносит обратно в 2.x или 3.6- или b) вас волнуют проблемы, перечисленные в Будет ли OrderedDict станет избыточным в Python 3.7? ). И обратно в 3.6, порядок вставки словаря был гарантирован реализацией CPython (но не языковой спецификацией).
smci
1
@smci В Python 3.7 диктанты следуют порядку вставки, однако они все еще отличаются от OrderedDict. диктанты игнорируют порядок, когда их сравнивают на равенство, тогда как OrderedDictпринимают во внимание порядок при сравнении. Я знаю, что вы ссылались на что-то, что объясняет это, но я подумал, что ваш комментарий мог ввести в заблуждение тех, кто, возможно, не читал эту ссылку.
Flimm
@Flimm: это правда, но я не вижу, чтобы это имело значение, этот вопрос задает два вопроса в одном, и цель второй части была заменена. «Дикты игнорируют порядок, когда их сравнивают на равенство» Да, потому что они не должны сравниваться по порядку. "тогда как OrderedDictпри сравнении учитывает порядок" Хорошо, но никому нет дела после 3.7. Я утверждаю, OrderedDictчто в значительной степени устарел, можете ли вы сформулировать причины, почему это не так? например, здесь не убедительно, если вам не нужноreversed()
smci
@Flimm: Я не могу найти никаких достоверных аргументов, почему OrderedDict не устарел в коде в 3.7+, если он не должен быть совместим с pre-3.7 или 2.x. Так, например, это совсем не убедительно. В частности, «использование OrderedDict сообщает о ваших намерениях ...» является ужасающим аргументом в пользу абсолютно необходимого технического долга и обфускации. Люди должны просто переключиться на диктовку и продолжать с этим. Так просто.
Smci

Ответы:

713

Для обычного dict вы можете использовать:

mydict[new_key] = mydict.pop(old_key)

Для OrderedDict, я думаю, вы должны создать совершенно новый, используя понимание.

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

Модификация самого ключа, как кажется, задает этот вопрос, нецелесообразна, поскольку ключи диктовки обычно являются неизменяемыми объектами, такими как числа, строки или кортежи. Вместо того, чтобы пытаться изменить ключ, переназначение значения новому ключу и удаление старого ключа - это то, как вы можете добиться «переименования» в python.

Wim
источник
Для Python 3.5, я думаю, что dict.popэто работает для OrderedDict на основе моего теста.
Даниэль
5
Нет, OrderedDictсохранится порядок вставки, о котором вопрос не задан.
Вим
64

лучший метод в 1 строке:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}
Tcll
источник
3
что если у вас есть d = {('test', 'foo'):[0,1,2]}?
Петр Федосов
4
@PetrFedosov, тогда вы делаетеd[('new', 'key')] = d.pop(('test', 'foo'))
Роберт Симер
17
Этот ответ пришел через два года после ответа Вима, который предполагает то же самое, без дополнительной информации. Что мне не хватает?
Андрас Дик
@AndrasDeak разница между dict () и OrderedDict ()
Tcll
4
Ответ Вима в его первоначальной редакции от 2013 года , с тех пор были только дополнения. Упорядоченность исходит только из критерия ОП.
Андрас Дик
29

Используя проверку newkey!=oldkey, вы можете сделать следующее:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]
Сринивас Редди Татиарти
источник
4
это прекрасно работает, но не поддерживает первоначальный порядок, потому что новый ключ добавляется в конце по умолчанию (в python3).
Сейтлин
2
@szeitlin, вы не должны полагаться на dictпорядок, даже если python3.6 + внедряет его в упорядоченную форму, предыдущие версии этого не делают, и это на самом деле не просто следствие того, как изменились вложения py3.6 +. Используйте, OrderedDictесли вы заботитесь о порядке.
Гранитозавр
2
Спасибо @Granitosaurus, мне не нужно, чтобы вы мне это объяснили. Это был не смысл моего комментария.
Сейтлин
5
@szeitlin Ваш комментарий подразумевает, что тот факт, что он изменяет порядок вложения, имеет какое-то значение, форму или форму, когда в действительности никто не должен полагаться на порядок в словаре, так что этот факт совершенно не имеет значения
Granitosaurus
11

В случае переименования всех ключей словаря:

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()гарантированно быть такой же порядок , как и в определении? Я думал, что Python dict неупорядочен, а порядок с точки зрения ключей dict непредсказуем
ttimasdf
Я думаю, вы должны отсортировать ключи в какой-то момент ...
Espoir Murhabazi
10

Вы можете использовать это, OrderedDict recipeнаписанное Raymond Hettinger, и изменить его, чтобы добавить renameметод, но по сложности это будет O (N):

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

Пример:

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

вывод:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
Ашвини Чаудхари
источник
1
@Mose Добавить файл Python где-нибудь в пути поиска вашего модуля и импортировать OrderedDictиз него. Но я бы порекомендовал: создать класс, который наследуется от него, OrderedDictи добавить renameк нему метод.
Ашвини Чаудхари
Похоже, с тех пор OrderedDict был переписан для использования двусвязного списка, так что, вероятно, есть еще способ сделать это, но для этого требуется гораздо больше акробатики. : - /
szeitlin
6

Несколько человек до меня упомянули .popтрюк, чтобы удалить и создать ключ в одну строку.

Лично я нахожу более явную реализацию более читабельной:

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

Код выше возвращает {'a': 1, 'c': 2}

Ури Горен
источник
1

Другие ответы довольно хороши. Но в python3.6 регулярный dict также имеет порядок. Поэтому трудно удерживать положение клавиши в обычном случае.

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
источник
1

В Python 3.6 (далее?) Я бы пошел на следующую однострочную

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

который производит

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

Может быть стоит отметить, что без printзаявления ноутбук ipython console / jupyter представляет словарь в порядке их выбора ...

KeithWM
источник
0

Я придумал эту функцию, которая не изменяет оригинальный словарь. Эта функция также поддерживает список словарей.

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

Я использую ответ @wim выше, с dict.pop () при переименовании ключей, но я нашел ошибку. Циклическое переключение между диктовкой для изменения ключей без полного отделения списка старых ключей от экземпляра dict приводило к циклическому изменению новых, измененных ключей в цикле и отсутствию некоторых существующих ключей.

Для начала я сделал это так:

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

Я обнаружил, что, перебирая диктовку таким образом, словарь продолжал находить ключи, даже когда он не должен был, то есть новые ключи, те, которые я изменил! Мне нужно было полностью отделить экземпляры друг от друга, чтобы (а) избежать нахождения собственных измененных ключей в цикле for и (б) найти некоторые ключи, которые по какой-то причине не были найдены в цикле.

Я делаю это сейчас:

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

Преобразование my_dict.keys () в список было необходимо, чтобы освободить ссылку на изменяющийся диктат. Простое использование my_dict.keys () позволило мне быть привязанным к оригинальному экземпляру со странными побочными эффектами.

excyberlabber
источник
-1

В случае, если кто-то хочет переименовать все ключи сразу, предоставив список с новыми именами:

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 Мне нравится твоя функция. Вот модификация для переименования нескольких ключей за один вызов:

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
источник
-2

Предположим, вы хотите переименовать ключ k3 в k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Шишир
источник
1
Не работает, вы имели ввиду ... temp_dict ['k4'] = temp_dict.pop ('k3')?
ДугР
Это вернет: TypeError: 'dict' object is not callableЕсли temp_dict('k3')вы пытаетесь сослаться на значение k3ключа, вам следует использовать квадратные скобки, а не круглые скобки. Однако это просто добавит новый ключ в словарь и не будет переименовывать существующий ключ в соответствии с запросом.
Жасмин