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


400

Чи є спосіб перейменувати ключ словника, не присвоюючи його значення новому імені та не видаляючи старий ключ імені; і без ітерації через ключ / значення dict?

У разі OrdersDict зробіть те саме, зберігаючи позицію цього ключа.


7
що саме ви маєте на увазі під "не переназначаючи своє значення новому імені та не видаляючи старий ключ імені"? як я бачу, це визначення перейменування ключа, а всі відповіді нижче перепризначте значення та видаліть стару назву ключа. ви ще не повинні прийняти відповідь, тож, можливо, вони не досягли того, чого ви шукаєте?
dbliss

5
Вам дійсно потрібно вказати номер (и) версії. Як і в Python 3.7, мовна специфікація тепер гарантує, що дикти дотримуються порядку вставки . Це також робить OrdersDict в основному застарілим (якщо тільки а) ви не хочете, щоб код, який також підтримує значення 2.x або 3.6- або b) переймався питаннями, переліченими в Чи стане OrdersDict зайвим у Python 3.7? ). І ще в 3.6, порядок вставки словника був гарантований реалізацією CPython (але не мовою).
smci

1
@smci У Python 3.7 дикти дотримуються порядку вставки, однак вони все ще відрізняються від OrderedDict. диктує ігнорувати порядок, коли їх порівнюють за рівність, тоді як OrderedDictвраховують порядок при порівнянні. Я знаю, що ви пов’язали щось, що пояснює це, але я подумав, що ваш коментар може ввести в оману тих, хто, можливо, не прочитав це посилання.
Flimm

@Flimm: це правда, але я не можу побачити, що це має значення, це питання задає два питання в одному, а наміри другої частини були замінені. "диктує ігнорування порядку, коли їх порівнюють за рівність" Так, тому що вони не повинні порівнювати за порядком. "тоді як OrderedDictвраховує порядок при порівнянні" Добре, але ніхто не піклується про пост-3.7. Я стверджую, що OrderedDictце значною мірою застарілий, чи можете ви сформулювати причини, чому це не так? наприклад, тут не є переконливим, якщо вам не потрібноreversed()
smci

@Flimm: Я не можу знайти жодних достовірних аргументів, чому OrdersDict не застарів у коді в версії 3.7+, якщо він не повинен бути сумісний з попередніми 3.7 або 2.x. Так, наприклад, це зовсім не переконливо. Зокрема, "використання OrdersDict повідомляє про ваш намір ..." - жахливий аргумент для цілком необхідної технічної заборгованості та заплутаності. Люди повинні просто переключитися назад, щоб диктувати, і продовжувати це. Це просто.
smci

Відповіді:


712

Для звичайного дикту ви можете використовувати:

mydict[new_key] = mydict.pop(old_key)

Для OrdersDict я думаю, що ви повинні побудувати абсолютно новий, використовуючи розуміння.

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


Для python 3.5, dict.popна мою тесту , я думаю, що це підходить для OrdersDict.
Даніель

5
Ні, OrderedDictзбережеться порядок вставки, про що не йдеться.
вім

64

найкращий метод в 1 рядку:

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

3
що якщо у вас є d = {('test', 'foo'):[0,1,2]}?
Петро Федосов

4
@PetrFedosov, тоді тиd[('new', 'key')] = d.pop(('test', 'foo'))
Роберт Сімер

17
Ця відповідь надійшла через два роки після відповіді Wim, яка говорить про те саме, без додаткової інформації. Що я пропускаю?
Андрій Дік

@AndrasDeak різниця між dict () та OrriedDict ()
Tcll

4
відповідь wim у своєму первинному перегляді з 2013 року , з тих пір були лише доповнення. Упорядкованість випливає лише з критерію ОП.
Дерас Андрас

29

За допомогою чека на newkey!=oldkeyце ви можете:

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

4
це працює чудово, але не підтримує початковий порядок, оскільки новий ключ додається наприкінці за замовчуванням (у python3).
szeitlin

2
@szeitlin ви не повинні покладатися на dictзамовлення, навіть якщо python3.6 + інтилізує його в упорядкованому вигляді, попередні версії цього не роблять, і це насправді не є лише наслідком того, як змінилися диктати py3.6 +. Використовуйте, OrderedDictякщо ви дбаєте про замовлення.
Гранітозавр

2
Дякую @Granitosaurus, мені не потрібно було мені це пояснювати. Це не було моїм коментарем.
szeitlin

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 не має порядку, і наказ з точки зору ключів
диктату

Я думаю, вам слід впорядкувати свої ключі в якийсь момент ...
Еспоар Мурхабазі

10

Ви можете використовувати це, OrderedDict recipeнаписане Реймоном Хеттінгером, і модифікувати його, щоб додати 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
@MERose Додайте файл Python десь у шляху пошуку вашого модуля та імпортуйте OrderedDictз нього. Але я б рекомендував: Створіть клас, який успадковує, OrderedDictі додайте renameдо нього метод.
Ашвіні Шадхарі

Здається, OrdersDict з тих пір переписаний для використання подвійно пов'язаного списку, тому, мабуть, ще існує спосіб зробити це, але для цього потрібно набагато більше акробатики. : - /
szeitlin

6

Кілька людей до мене згадували про .popхитрість видалити та створити ключ в одноколірці.

Я особисто вважаю більш чіткою реалізацію більш зрозумілою:

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

Верхній код повертається {'a': 1, 'c': 2}


1

Інші відповіді досить хороші. Але в python3.6, регулярний дикт також має порядок. Тому важко утримувати позицію ключа в звичайному випадку.

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

У 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 notebook notebook представляє словник у порядку їх вибору ...


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 () при перейменуванні клавіш, але я знайшов gotcha. Перемикання через дікт для зміни клавіш, не відокремлюючи список старих ключів від екземпляра дикту, призвело до переключення нових, змінених ключів у цикл та відсутності деяких існуючих ключів.

Для початку я зробив це так:

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

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

Я роблю це зараз:

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

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


-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

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

Це повернеться TypeError: 'dict' object is not callableЯкщо якщо temp_dict('k3')ви намагаєтеся посилатися на значення k3ключа, то вам слід використовувати квадратні дужки, а не дужки. Однак це просто додасть новий ключ до словника і не буде перейменовувати існуючий ключ, як вимагається.
Жасмін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.