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.


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.


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]}

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]

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)

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

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}


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

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


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

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


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

-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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.