Bagaimana cara menghapus item dari kamus saat iterasi?

295

Apakah sah untuk menghapus item dari kamus dengan Python saat iterasi?

Sebagai contoh:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

Idenya adalah untuk menghapus elemen yang tidak memenuhi kondisi tertentu dari kamus, alih-alih membuat kamus baru yang merupakan bagian dari yang sedang diulangi.

Apakah ini solusi yang baik? Apakah ada cara yang lebih elegan / efisien?

Trilarion
sumber
1
Pertanyaan terkait dengan jawaban yang sangat menarik: stackoverflow.com/questions/9023078/… .
Maks
Orang bisa mencoba dengan mudah. Jika gagal, itu tidak sah.
Trilarion
26
@Trilarion One bisa mencoba dengan mudah ... dan dengan mudah belajar apa pun yang berharga. Jika berhasil, itu belum tentu sah. Kasing tepi dan peringatan tak terduga berlimpah. Pertanyaan ini tidak menarik bagi semua calon Pythonistas. Pemecatan dengan tangan melambai atas perintah "Seseorang bisa mencoba dengan mudah!" tidak membantu dan bertentangan dengan semangat ingin tahu dari pertanyaan stackoverflow.
Cecil Curry
Setelah meneliti max 's pertanyaan yang terkait , saya harus setuju. Anda mungkin hanya ingin membaca dengan cermat pertanyaan mendalam yang mengganggu itu dan jawabannya yang ditulis dengan baik. Pikiran Pythonic Anda akan meledak.
Cecil Curry
1
@ CecilCurry Menguji ide untuk diri Anda sendiri sebelum mempresentasikannya di sini adalah semacam semangat stackoverflow jika saya tidak salah. Hanya itu yang ingin saya sampaikan. Maaf jika ada gangguan karena itu. Juga saya pikir ini adalah pertanyaan yang bagus dan tidak membatalkannya. Saya paling suka jawaban Jochen Ritzel . Saya tidak berpikir seseorang perlu melakukan semua hal itu untuk menghapus dengan cepat ketika menghapus pada langkah kedua jauh lebih sederhana. Itu harus menjadi cara yang disukai dalam pandangan saya.
Trilarion

Jawaban:

305

EDIT:

Jawaban ini tidak akan berfungsi untuk Python3 dan akan memberikan RuntimeError.

RuntimeError: kamus berubah ukuran saat iterasi.

Ini terjadi karena mydict.keys()mengembalikan iterator bukan daftar. Seperti yang ditunjukkan dalam komentar cukup mengkonversi mydict.keys()ke daftar oleh list(mydict.keys())dan itu harus berfungsi.


Tes sederhana di konsol menunjukkan Anda tidak dapat memodifikasi kamus saat iterating di atasnya:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

Seperti yang dinyatakan dalam jawaban delnan, menghapus entri menyebabkan masalah ketika iterator mencoba untuk pindah ke entri berikutnya. Sebagai gantinya, gunakan keys()metode untuk mendapatkan daftar kunci dan bekerja dengan itu:

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}

Jika Anda perlu menghapus berdasarkan nilai item, gunakan items()metode ini sebagai gantinya:

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}
Blair
sumber
53
Perhatikan bahwa dalam Python 3, dict.items () mengembalikan iterator (dan dict.iteritems () hilang).
Tim Lesher
83
Untuk menguraikan komentar @TimLesher ... Ini TIDAK akan bekerja dengan Python 3.
maks
99
Untuk menguraikan elaborasi @ max, ini akan berfungsi jika Anda mengonversi kode di atas dengan 2to3. Salah satu pemecah masalah default akan membuat loop terlihat seperti for k, v in list(mydict.items()):yang berfungsi dengan baik di Python 3. Sama untuk keys()menjadi list(keys()).
Walter Mundt
8
Ini tidak berfungsi. Saya mendapat pesan kesalahan:RuntimeError: dictionary changed size during iteration
Tomáš Zato - Reinstate Monica
15
@ TomášZato seperti yang ditunjukkan Walter, untuk python3 Anda perlu menggunakan for k in list(mydict.keys()): sebagai python3 membuat kunci () metode iterator, dan juga melarang menghapus item dict selama iterasi. Dengan menambahkan panggilan daftar () Anda mengubah kunci () iterator menjadi daftar. Jadi, ketika Anda berada di badan for loop Anda tidak lagi beralih ke kamus itu sendiri.
Geoff Crompton
89

Anda juga bisa melakukannya dalam dua langkah:

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

Pendekatan favorit saya biasanya hanya membuat dict baru:

# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)
Jochen Ritzel
sumber
11
@senderle: Sejak 2,7 sebenarnya.
Jochen Ritzel
5
Pendekatan pemahaman dikt membuat salinan kamus; untungnya nilai-nilai setidaknya tidak disalin, hanya ditautkan. Masih jika Anda memiliki banyak kunci, itu bisa menjadi buruk. Untuk alasan itu, saya lebih suka removependekatan loop.
Maks
1
Anda juga dapat menggabungkan langkah-langkah:for k in [k for k in mydict if k == val]: del mydict[k]
AXO
solusi pertama adalah satu-satunya yang efisien pada dikte besar di utas ini sejauh ini - karena tidak membuat salinan panjang penuh.
kxr
21

Anda tidak dapat mengubah koleksi saat mengulanginya. Dengan cara itu ada kegilaan - terutama, jika Anda diizinkan untuk menghapus dan menghapus item saat ini, iterator harus pindah (+1) dan panggilan berikutnya nextakan membawa Anda melampaui itu (+2), jadi Anda akan akhirnya melewatkan satu elemen (yang tepat di belakang yang Anda hapus). Anda memiliki dua opsi:

  • Salin semua kunci (atau nilai, atau keduanya, tergantung pada apa yang Anda butuhkan), lalu beralihlah ke sana. Anda dapat menggunakan .keys()et al untuk ini (dalam Python 3, meneruskan iterator yang dihasilkan ke list). Bisa jadi sangat boros ruang-bijaksana.
  • Iterate mydictseperti biasa, menyimpan kunci untuk dihapus dalam koleksi terpisah to_delete. Setelah selesai iterasi mydict, hapus semua item to_deletedari mydict. Menyimpan beberapa (tergantung pada berapa banyak tombol yang dihapus dan berapa banyak yang tersisa) ruang selama pendekatan pertama, tetapi juga membutuhkan beberapa baris lagi.

sumber
You can't modify a collection while iterating it.ini hanya benar untuk dikte dan teman, tetapi Anda dapat mengubah daftar selama iterasi:L = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
Nils Lindemann
3
@Nils Ini tidak membuang pengecualian tapi masih salah. Amati: codepad.org/Yz7rjDVT - lihat misalnya stackoverflow.com/q/6260089/395760 untuk penjelasan
Punya aku di sini. Tetap can'thanya benar untuk dict dan teman-teman, sementara itu harus shouldn'tuntuk daftar.
Nils Lindemann
21

Iterate alih-alih salinan, seperti yang dikembalikan oleh items():

for k, v in list(mydict.items()):
Ignacio Vazquez-Abrams
sumber
1
Itu tidak masuk akal - maka Anda tidak bisa del vsecara langsung, jadi Anda telah membuat salinan dari masing-masing v yang tidak akan pernah Anda gunakan dan Anda harus mengakses item dengan kunci. dict.keys()adalah pilihan yang lebih baik.
jscs
2
@ Josh: Itu semua tergantung pada seberapa banyak Anda akan perlu digunakan vsebagai kriteria untuk dihapus.
Ignacio Vazquez-Abrams
3
Di bawah Python 3, dict.items()mengembalikan iterator daripada salinan. Lihat komentar untuk Blair 's jawaban , yang (sayangnya) juga mengasumsikan Python 2 semantik.
Cecil Curry
11

Ini paling bersih untuk digunakan list(mydict):

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
{'four': 4, 'two': 2, 'one': 1}

Ini sesuai dengan struktur paralel untuk daftar:

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

Keduanya bekerja di python2 dan python3.

Randen
sumber
Ini tidak baik jika dataset Anda besar. Ini menyalin semua objek dalam memori, bukan?
AFP_555
1
@ AFP_555 Ya - tujuan saya di sini adalah untuk kode pythonic yang bersih, paralel. Jika Anda memerlukan efisiensi memori, pendekatan terbaik yang saya tahu adalah beralih dan buat daftar kunci untuk dihapus atau dikte item baru untuk disimpan. Kecantikan adalah prioritas saya dengan Python; untuk dataset besar saya menggunakan Go atau Rust.
rsanden
9

Anda dapat menggunakan pemahaman kamus.

d = {k:d[k] for k in d if d[k] != val}

Harun
sumber
Ini adalah yang paling Pythonic.
Yehosef
Tapi itu menciptakan kamus baru alih-alih memodifikasi ddi tempat.
Aristide
9

Dengan python3, iterate on dic.keys () akan meningkatkan kesalahan ukuran kamus. Anda dapat menggunakan cara alternatif ini:

Diuji dengan python3, itu berfungsi dengan baik dan Kesalahan " kamus berubah ukuran selama iterasi " tidak dinaikkan:

my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# {}
glihm
sumber
4

Pertama-tama Anda bisa membuat daftar kunci untuk dihapus, dan kemudian beralih dari daftar itu menghapusnya.

dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]
Pob
sumber
Ini merupakan duplikat dari solusi pertama @ Ritzel (efisien untuk perintah besar tanpa salinan lengkap). Meskipun "baca panjang" tanpa pemahaman daftar. Namun apakah ini mungkin lebih cepat?
kxr
3

Ada cara yang mungkin cocok jika item yang ingin Anda hapus selalu di "awal" dari iterasi dikte

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

"Permulaan" hanya dijamin konsisten untuk versi / implementasi Python tertentu. Misalnya dari What's New In Python 3.7

sifat pelestarian penyisipan-urutan objek dict telah dinyatakan sebagai bagian resmi dari spesifikasi bahasa Python.

Cara ini menghindari salinan dikt yang disarankan oleh banyak jawaban lain, setidaknya dalam Python 3.

Michal Charemza
sumber
1

Saya mencoba solusi di atas dalam Python3 tetapi yang satu ini tampaknya menjadi satu-satunya yang berfungsi untuk saya ketika menyimpan objek dalam dict. Pada dasarnya Anda membuat salinan dikt Anda () dan beralihi itu sambil menghapus entri dalam kamus asli Anda.

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
JasonLandbridge
sumber