Zmień nazwę klucza słownika

400

Czy istnieje sposób zmiany nazwy klucza słownika bez ponownego przypisywania jego wartości do nowej nazwy i usuwania starego klucza nazwy; i bez iteracji po kluczu / wartości dict?

W przypadku OragedDict, zrób to samo, zachowując pozycję tego klucza.

rabin utam
źródło
7
co dokładnie masz na myśli przez „bez przypisywania jego wartości do nowej nazwy i usuwania starego klucza nazwy”? sposób, w jaki go widzę, to jest definicja zmiany nazwy klucza, a wszystkie poniższe odpowiedzi ponownie przypisują wartość i usuwają starą nazwę klucza. musisz jeszcze zaakceptować odpowiedź, więc może nie osiągnęli tego, czego szukasz?
dbliss,
5
Naprawdę musisz podać numery wersji. Od wersji Python 3.7 specyfikacja języka gwarantuje teraz, że dykty będą zgodne z kolejnością wstawiania . To sprawia, że ​​OragedDict jest w większości przestarzały (chyba że a) chcesz kodu, który również backportuje się do wersji 2.x lub 3.6- lub b) zależy Ci na problemach wymienionych w Czy OrDERDict stanie się zbędny w Python 3.7? ). W wersji 3.6 kolejność wstawiania słowników była gwarantowana przez implementację CPython (ale nie specyfikację języka).
smci,
1
@smci W Pythonie 3.7 dykta są zgodne z kolejnością wstawiania, jednak wciąż różnią się od OrderedDict. dyktaty ignorują porządek, gdy są porównywane dla równości, natomiast OrderedDictuwzględniają porządek, gdy są porównywane. Wiem, że masz link do czegoś, co to wyjaśnia, ale pomyślałem, że twój komentarz mógł wprowadzić w błąd tych, którzy nie przeczytali tego linku.
Flimm,
@Flimm: to prawda, ale nie widzę, żeby to miało znaczenie, pytanie to zadaje dwa pytania w jednym, a zamiar drugiej części został zastąpiony. „dyktaty ignorują porządek, gdy są porównywane dla równości” Tak, ponieważ nie należy ich porównywać według kolejności. „mając na uwadze, że OrderedDictprzy porównywaniu brany jest pod uwagę porządek” Ok, ale nikogo to nie obchodzi po 3.7. Uważam, że OrderedDictjest w dużej mierze przestarzałe, czy możesz podać powody, dla których tak nie jest? np. tutaj nie przekonuje, chyba że potrzebujeszreversed()
smci
@Flimm: Nie mogę znaleźć żadnych wiarygodnych argumentów, dlaczego OragedDict nie jest przestarzały w kodzie w wersji 3.7+, chyba że musi być zgodny z wersją wcześniejszą niż 3.7 lub 2.x. Tak np to wcale nie jest przekonujący. W szczególności „używanie OrdictDict komunikuje twój zamiar ...” jest przerażającym argumentem za całkowicie niezbędnym długiem technicznym i zaciemnieniem. Ludzie powinni po prostu wrócić do dyktowania i zacząć z tym żyć. Tak proste.
smci,

Odpowiedzi:

712

Do zwykłego nagrania możesz użyć:

mydict[new_key] = mydict.pop(old_key)

Wydaje mi się, że w przypadku OrdersDict musisz zbudować zupełnie nowy, korzystając ze zrozumienia.

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

Modyfikacja samego klucza, jak wydaje się to pytanie, jest niepraktyczna, ponieważ klucze dict są zwykle niezmiennymi obiektami, takimi jak liczby, ciągi znaków lub krotki. Zamiast próbować modyfikować klucz, ponowne przypisanie wartości do nowego klucza i usunięcie starego klucza jest sposobem na osiągnięcie „zmiany nazwy” w pythonie.

wim
źródło
dict.popWydaje mi się, że w przypadku Pythona 3.5 możliwe jest użycie polecenia OragedDict na podstawie mojego testu.
Daniel
5
Nie, OrderedDictzachowa kolejność wstawiania, czego nie dotyczy pytanie.
wim
64

najlepsza metoda w 1 linii:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}
Tcll
źródło
3
Co jeśli d = {('test', 'foo'):[0,1,2]}?
Petr Fedosov
4
@PetrFedosov, a potem tyd[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer
17
Ta odpowiedź nadeszła dwa lata po odpowiedzi Wima, która sugeruje dokładnie to samo, bez dodatkowych informacji. czego mi brakuje?
Andras Deak,
@AndrasDeak różnicę między dict () a OrdersDict ()
Tcll
4
Odpowiedź Wima w jego pierwotnej wersji z 2013 r. , od tego czasu pojawiły się tylko dodatki. Uporządkowanie wynika wyłącznie z kryterium PO.
Andras Deak,
29

Korzystając z czeku newkey!=oldkey, możesz w ten sposób:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]
Srinivas Reddy Thatiparthy
źródło
4
działa to pięknie, ale nie zachowuje oryginalnej kolejności, ponieważ nowy klucz jest domyślnie dodawany na końcu (w python3).
szeitlin
2
@szeitlin nie powinieneś polegać na dictzamówieniu, nawet jeśli python3.6 + zainicjuje go w uporządkowanej formie, poprzednie wersje tego nie robią i nie jest to tak naprawdę cecha po prostu efektem zmian w dyktandach py3.6 +. Użyj, OrderedDictjeśli zależy Ci na porządku.
Granitozaur
2
Dzięki @Granitosaurus, nie potrzebowałem, żebyś mi to wyjaśnił. Nie o to mi chodziło w moim komentarzu.
szeitlin
5
@szeitlin twój komentarz sugerował, że fakt, że zmienia kolejność dykt, ma w pewien sposób, kształt lub formę, kiedy w rzeczywistości nikt nie powinien polegać na kolejności słownikowej, więc ten fakt jest całkowicie nieistotny
Granitosaurus
11

W przypadku zmiany nazwy wszystkich kluczy słownika:

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
źródło
1
Czy target_dict.keys()zagwarantowane jest takie samo zamówienie jak w definicji? Myślałem, że dykton w Pythonie jest nieuporządkowany, a kolejność z widoku kluczy dykta jest nieprzewidywalna
ttimasdf
Myślę, że powinieneś posortować klucze w pewnym momencie ...
Espoir Murhabazi
10

Możesz użyć tego OrderedDict recipenapisanego przez Raymonda Hettingera i zmodyfikować go, aby dodać renamemetodę, ale będzie to złożoność 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

Przykład:

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

wynik:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
Ashwini Chaudhary
źródło
1
@MERose Dodaj plik Python gdzieś w ścieżce wyszukiwania modułów i zaimportuj OrderedDictgo. Ale polecam: Utwórz klasę, która będzie dziedziczyła OrderedDicti dodaj renamedo niej metodę.
Ashwini Chaudhary
Wygląda na to, że od tego czasu OrdersDict zostało przepisane na podwójnie połączoną listę, więc prawdopodobnie nadal jest na to sposób, ale wymaga dużo więcej akrobatyki. : - /
szeitlin
6

Kilka osób przede mną wspomniało o .popsztuczce polegającej na usunięciu i utworzeniu klucza w jednym wierszu.

Osobiście uważam, że bardziej wyraźna implementacja jest bardziej czytelna:

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

Powyższy kod zwraca {'a': 1, 'c': 2}

Uri Goren
źródło
1

Inne odpowiedzi są całkiem dobre, ale w python3.6 zwykły dict ma również porządek. Trudno więc utrzymać pozycję klucza w normalnym przypadku.

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
źródło
1

W Pythonie 3.6 (dalej?) Wybrałbym następującą linijkę

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

który produkuje

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

Warto zauważyć, że bez tej printinstrukcji notebook ipython console / jupyter prezentuje słownik w wybranej przez siebie kolejności ...

KeithWM
źródło
0

Wymyśliłem tę funkcję, która nie mutuje oryginalnego słownika. Ta funkcja obsługuje także listę słowników.

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
źródło
-1

Korzystam z powyższej odpowiedzi @wim, z dict.pop () podczas zmiany nazw kluczy, ale znalazłem gotcha. Cykliczne przejście przez dykta w celu zmiany kluczy, bez oddzielania listy starych kluczy całkowicie od instancji dict, spowodowało cykliczne wprowadzanie nowych, zmienionych kluczy do pętli i brak niektórych istniejących kluczy.

Na początek zrobiłem to w ten sposób:

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

Odkryłem, że w ten sposób przechodząc przez dyktat, słownik ciągle znajdował klucze, nawet gdy nie powinien, tj. Nowe klucze, te, które zmieniłem! Musiałem całkowicie oddzielić instancje od siebie, aby (a) uniknąć znalezienia własnych zmienionych kluczy w pętli for i (b) znaleźć niektóre klucze, które z jakiegoś powodu nie zostały znalezione w pętli.

Robię to teraz:

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

Konwersja my_dict.keys () na listę była konieczna, aby uwolnić się od odniesienia do zmieniającego się dykta. Samo użycie my_dict.keys () utrzymywało mnie przywiązanym do oryginalnej instancji z dziwnymi efektami ubocznymi.

excyberlabber
źródło
-1

W przypadku, gdy ktoś chce zmienić nazwy wszystkich kluczy jednocześnie, podając listę z nowymi nazwami:

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
źródło
-1

@ helloswift123 Podoba mi się twoja funkcja. Oto modyfikacja zmiany nazwy wielu kluczy w jednym wywołaniu:

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
źródło
-2

Załóżmy, że chcesz zmienić nazwę klucza z k3 na k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Shishir
źródło
1
Nie działa, czy miałeś na myśli ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR
Zwróci to a TypeError: 'dict' object is not callableJeśli temp_dict('k3')próbujesz odwołać się do wartości k3klucza, powinieneś użyć nawiasów kwadratowych, a nie nawiasów. Spowoduje to jednak dodanie nowego klucza do słownika i nie zmieni nazwy istniejącego klucza, zgodnie z żądaniem.
Jasmine