Ganti nama kunci kamus

401

Apakah ada cara untuk mengganti nama kunci kamus, tanpa menetapkan kembali nilainya ke nama baru dan menghapus kunci nama lama; dan tanpa iterasi melalui kunci / nilai dict?

Dalam kasus OrderedDict, lakukan hal yang sama, sambil menjaga posisi kunci itu.

rabin utam
sumber
7
apa sebenarnya yang Anda maksud dengan "tanpa menetapkan kembali nilainya ke nama baru dan menghapus kunci nama lama"? cara saya melihatnya, itu adalah definisi pengubahan nama kunci, dan semua jawaban di bawah menetapkan kembali nilai dan menghapus nama kunci yang lama. Anda belum menerima jawaban, jadi mungkin ini belum mencapai apa yang Anda cari?
dbliss
5
Anda benar-benar perlu menentukan nomor versi. Pada Python 3.7, spec bahasa sekarang menjamin bahwa dikt mengikuti perintah penyisipan . Itu juga membuat OrderedDict sebagian besar usang (kecuali a) Anda ingin kode yang juga backports ke 2.x atau 3.6- atau b) Anda peduli dengan masalah yang tercantum dalam Will OrderedDict menjadi mubazir dalam Python 3.7? ). Dan kembali pada 3.6, urutan penyisipan kamus dijamin oleh implementasi CPython (tetapi bukan spesifikasi bahasa).
smci
1
@smci Dalam Python 3.7 dicts ikuti urutan penyisipan, namun masih ada yang berbeda dari OrderedDict. dikte mengabaikan ketertiban ketika mereka dibandingkan untuk kesetaraan, sedangkan OrderedDictmemperhitungkannya ketika dibandingkan. Saya tahu Anda menautkan ke sesuatu yang menjelaskannya, tetapi saya pikir komentar Anda mungkin telah menyesatkan mereka yang mungkin tidak membaca tautan itu.
Flimm
@ Flimm: itu benar tetapi saya tidak dapat melihat bahwa itu penting, pertanyaan ini mengajukan dua pertanyaan-dalam-satu, dan maksud dari bagian kedua telah digantikan. "dikte mengabaikan pesanan ketika mereka dibandingkan untuk kesetaraan" Ya karena mereka tidak seharusnya dibandingkan dengan pesanan. "padahal OrderedDictmemperhitungkan pesanan ketika dibandingkan" Ok tapi tidak ada yang peduli pasca-3.7. Saya mengklaim OrderedDictsebagian besar sudah usang, dapatkah Anda mengartikulasikan alasan mengapa tidak? misalnya di sini tidak persuasif kecuali Anda perlureversed()
smci
@ Flimm: Saya tidak dapat menemukan argumen yang kredibel mengapa OrderedDict tidak usang pada kode dalam 3.7+ kecuali harus kompatibel dengan pra-3.7 atau 2.x. Jadi misal ini sama sekali tidak persuasif. Khususnya "menggunakan OrderedDict mengkomunikasikan niat Anda ..." adalah argumen yang mengerikan untuk utang teknis dan kebingungan yang benar-benar diperlukan. Orang hanya harus beralih kembali ke dikt dan melanjutkannya. Sesederhana itu.
smci

Jawaban:

712

Untuk dikte reguler, Anda dapat menggunakan:

mydict[new_key] = mydict.pop(old_key)

Untuk OrderedDict, saya pikir Anda harus membangun yang sama sekali baru menggunakan pemahaman.

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

Mengubah kunci itu sendiri, seperti yang tampaknya ditanyakan oleh pertanyaan ini, tidak praktis karena kunci dikt biasanya merupakan objek yang tidak dapat diubah seperti angka, string, atau tupel. Alih-alih mencoba memodifikasi kunci, menugaskan kembali nilai ke kunci baru dan menghapus kunci lama adalah bagaimana Anda dapat mencapai "ganti nama" dengan python.

wim
sumber
Untuk python 3.5, saya pikir dict.popbisa diterapkan untuk OrderedDict berdasarkan pengujian saya.
Daniel
5
Tidak, OrderedDictakan mempertahankan urutan penyisipan, yang bukan pertanyaan yang ditanyakan.
wim
64

metode terbaik dalam 1 baris:

>>> d = {'test':[0,1,2]}
>>> d['test2'] = d.pop('test')
>>> d
{'test2': [0, 1, 2]}
Tcll
sumber
3
apa jika Anda memiliki d = {('test', 'foo'):[0,1,2]}?
Petr Fedosov
4
@PetrFedosov, maka Anda lakukand[('new', 'key')] = d.pop(('test', 'foo'))
Robert Siemer
17
Jawaban ini datang dua tahun setelah jawaban wim yang menunjukkan hal yang persis sama, tanpa informasi tambahan. Apa yang saya lewatkan?
Andras Deak
@AndrasDeak perbedaan antara dict () dan OrderedDict ()
Tcll
4
Jawaban wim dalam revisi awalnya dari 2013 , hanya ada penambahan sejak itu. Keteraturan hanya berasal dari kriteria OP.
Andras Deak
29

Menggunakan tanda centang untuk newkey!=oldkey, cara ini dapat Anda lakukan:

if newkey!=oldkey:  
    dictionary[newkey] = dictionary[oldkey]
    del dictionary[oldkey]
Srinivas Reddy Thatiparthy
sumber
4
ini berfungsi dengan baik, tetapi tidak mempertahankan urutan asli karena kunci baru akan ditambahkan pada akhirnya secara default (dalam python3).
szeitlin
2
@szeitlin Anda tidak harus bergantung pada dictpesanan, bahkan jika python3.6 + menginternalisasi itu dalam bentuk yang dipesan, versi sebelumnya tidak dan itu bukan fitur hanya efek samping dari bagaimana perubahan py3.6 + dikt. Gunakan OrderedDictjika Anda peduli tentang pesanan.
Granitosaurus
2
Terima kasih @Granitosaurus, saya tidak perlu Anda menjelaskannya kepada saya. Itu bukan poin dari komentar saya.
szeitlin
5
@szeitlin komentar Anda menyiratkan bahwa fakta bahwa itu mengubah urutan perintah dalam beberapa cara, bentuk atau bentuk ketika pada kenyataannya tidak ada yang harus bergantung pada urutan kamus sehingga fakta ini sama sekali tidak relevan
Granitosaurus
11

Jika mengganti nama semua kunci kamus:

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
sumber
1
Apakah target_dict.keys()dijamin sama dengan definisi? Saya pikir dict Python tidak teratur, dan urutan dari tampilan kunci dict tidak dapat diprediksi
ttimasdf
Saya pikir Anda harus mengurutkan kunci Anda di beberapa titik ...
Espoir Murhabazi
10

Anda dapat menggunakan ini yang OrderedDict recipeditulis oleh Raymond Hettinger dan memodifikasinya untuk menambahkan renamemetode, tetapi ini akan menjadi O (N) dalam kompleksitas:

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

Contoh:

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

keluaran:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
Ashwini Chaudhary
sumber
1
@MERose Tambahkan file Python di suatu tempat di jalur pencarian modul Anda dan impor OrderedDictdari itu. Tetapi saya akan merekomendasikan: Buat kelas yang mewarisi dari OrderedDictdan menambahkan renamemetode untuk itu.
Ashwini Chaudhary
Sepertinya OrderedDict telah ditulis ulang untuk menggunakan daftar yang ditautkan dua kali lipat, jadi mungkin masih ada cara untuk melakukan ini, tetapi itu membutuhkan lebih banyak akrobat. : - /
szeitlin
6

Beberapa orang sebelum saya menyebutkan .pop trik untuk menghapus dan membuat kunci dalam satu baris.

Saya pribadi menemukan implementasi yang lebih eksplisit lebih mudah dibaca:

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

Kode di atas kembali {'a': 1, 'c': 2}

Uri Goren
sumber
1

Jawaban lain cukup bagus. Namun dalam python3.6, perintah reguler juga memiliki urutan. Jadi sulit untuk mempertahankan posisi kunci dalam kasus 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
sumber
1

Dalam Python 3.6 (seterusnya?) Saya akan pergi untuk satu-liner berikut

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

yang menghasilkan

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

Mungkin perlu dicatat bahwa tanpa printpernyataan konsol ipython / notebook jupyter menyajikan kamus dalam urutan pilihan mereka ...

KeithWM
sumber
0

Saya datang dengan fungsi ini yang tidak mengubah kamus asli. Fungsi ini juga mendukung daftar kamus juga.

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
sumber
-1

Saya menggunakan jawaban @wim di atas, dengan dict.pop () ketika mengganti nama kunci, tetapi saya menemukan gotcha. Bersepeda melalui dict untuk mengubah kunci, tanpa memisahkan daftar kunci lama sepenuhnya dari instance dict, menghasilkan bersepeda baru, mengubah kunci menjadi loop, dan kehilangan beberapa kunci yang ada.

Untuk memulainya, saya melakukannya dengan cara ini:

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

Saya menemukan bahwa bersepeda melalui dikt dengan cara ini, kamus terus menemukan kunci bahkan ketika seharusnya tidak, yaitu kunci baru, yang telah saya ubah! Saya perlu memisahkan instans sepenuhnya dari satu sama lain untuk (a) menghindari menemukan kunci saya sendiri yang diubah dalam for loop, dan (b) menemukan beberapa kunci yang tidak ditemukan dalam loop karena suatu alasan.

Saya melakukan ini sekarang:

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

Mengubah my_dict.keys () ke daftar diperlukan untuk membebaskan referensi ke perubahan dict. Hanya menggunakan my_dict.keys () membuat saya terikat dengan contoh aslinya, dengan efek samping yang aneh.

excyberlabber
sumber
-1

Jika seseorang ingin mengganti nama semua kunci sekaligus memberikan daftar dengan nama baru:

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
sumber
-1

@ helloswift123 Saya suka fungsi Anda. Berikut ini adalah modifikasi untuk mengganti nama beberapa kunci dalam satu panggilan:

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
sumber
-2

Misalkan Anda ingin mengganti nama kunci k3 menjadi k4:

temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Shishir
sumber
1
Tidak berfungsi, maksud Anda ... temp_dict ['k4'] = temp_dict.pop ('k3')?
DougR
Ini akan mengembalikan TypeError: 'dict' object is not callableJika dengan temp_dict('k3')Anda mencoba untuk referensi nilai k3kunci maka Anda harus menggunakan tanda kurung dan bukan tanda kurung. Namun, ini hanya akan menambahkan kunci baru ke kamus dan tidak akan mengganti nama kunci yang ada, seperti yang diminta.
Jasmine