Menghapus beberapa kunci dari kamus dengan aman

128

Saya tahu untuk menghapus entri, 'kunci' dari kamus saya d, dengan aman, Anda melakukannya:

if d.has_key('key'):
    del d['key']

Namun, saya perlu menghapus beberapa entri dari kamus dengan aman. Saya sedang berpikir untuk mendefinisikan entri dalam tupel karena saya perlu melakukan ini lebih dari sekali.

entitiesToREmove = ('a', 'b', 'c')
for x in entitiesToRemove:
    if d.has_key(x):
        del d[x]

Namun, saya bertanya-tanya apakah ada cara yang lebih cerdas untuk melakukan ini?

dublintech.dll
sumber
3
Waktu pengambilan dari kamus hampir O (1) karena hashing. Kecuali Anda menghapus sebagian besar entri, menurut saya Anda tidak akan melakukannya lebih baik.
ncmathsadist
1
Jawaban @mattbornski tampaknya lebih kanonik, dan juga lebih ringkas.
Ioannis Filippidis
2
StackOverflow telah berbicara: key in dlebih Pythonic daripada d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper
Jika Anda dapat menyisihkan sedikit memori, Anda dapat melakukannya for x in set(d) & entities_to_remove: del d[x]. Ini mungkin hanya akan lebih efisien jika entities_to_remove"besar".
DylanYoung

Jawaban:

56

Kenapa tidak seperti ini:

entries = ('a', 'b', 'c')
the_dict = {'b': 'foo'}

def entries_to_remove(entries, the_dict):
    for key in entries:
        if key in the_dict:
            del the_dict[key]

Versi yang lebih ringkas disediakan oleh mattbornski menggunakan dict.pop ()

Glaslos
sumber
14
Menambahkan ini untuk orang yang datang dari mesin telusur. Jika kunci diketahui (saat keamanan tidak menjadi masalah), beberapa kunci dapat dihapus dalam satu baris seperti inidel dict['key1'], dict['key2'], dict['key3']
Tirtha R
Bergantung pada jumlah kunci yang Anda hapus, mungkin lebih efisien untuk menggunakan for key in set(the_dict) & entries:dan melewati key in dictpengujian.
DylanYoung
236
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
mattbornski
sumber
38
Ini. Ini adalah pilihan Pythonista yang cerdas. dict.pop()menghilangkan kebutuhan untuk pengujian keberadaan kunci. Luar biasa.
Cecil Curry
4
Untuk apa nilainya, saya pikir .pop()itu buruk dan unpythonic, dan lebih suka jawaban yang diterima daripada yang ini.
Arne
5
Sejumlah besar orang tampak tidak terganggu oleh ini :) Saya tidak keberatan dengan baris tambahan untuk pemeriksaan keberadaan secara pribadi, dan itu jauh lebih mudah dibaca kecuali Anda sudah tahu tentang pop (). Di sisi lain, jika Anda mencoba melakukan ini dalam pemahaman atau lambda sebaris, trik ini bisa sangat membantu. Menurut saya, penting juga untuk bertemu orang-orang di mana mereka berada. Saya tidak yakin bahwa "bad and unpythonic" akan memberi orang-orang yang membaca jawaban ini panduan praktis yang mereka cari.
mattbornski
5
Ada alasan yang sangat bagus untuk menggunakan ini. Meskipun menambahkan baris tambahan dapat meningkatkan "keterbacaan" atau "kejelasan", ini juga menambahkan pencarian ekstra ke kamus. Metode ini setara dengan melakukan penghapusan setdefault. Jika diimplementasikan dengan benar (dan saya yakin itu), itu hanya melakukan satu pencarian ke dalam peta-hash yaitu dict, bukan dua.
Fisikawan Gila
2
Secara pribadi saya akan prihatin dengan kebenaran dan pemeliharaan terlebih dahulu, dan kecepatan hanya jika terbukti tidak cukup cepat. Perbedaan kecepatan antara operasi ini akan menjadi sepele jika diperbesar ke tingkat aplikasi. Mungkin kasusnya lebih cepat, tetapi saya berharap bahwa dalam penggunaan dunia nyata Anda tidak akan memperhatikan atau peduli, dan jika Anda memperhatikan dan peduli, Anda akan lebih baik disajikan menulis ulang dalam sesuatu yang lebih berkinerja daripada Python.
mattbornski
90

Menggunakan Dict Comprehensions

final_dict = {key: t[key] for key in t if key not in [key1, key2]}

di mana key1 dan key2 harus dihapus.

Pada contoh di bawah ini, kunci "b" dan "c" harus dihapus & disimpan dalam daftar kunci.

>>> a
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> keys = ["b", "c"]
>>> print {key: a[key] for key in a if key not in keys}
{'a': 1, 'd': 4}
>>> 
Abhijeet Rastogi
sumber
4
kamus baru? pemahaman daftar? Anda harus menyesuaikan jawaban untuk orang yang mengajukan pertanyaan;)
Glaslos
6
Solusi ini memiliki kinerja yang serius ketika variabel yang menahan telah digunakan lebih lanjut dalam program. Dengan kata lain, dikt dari mana kunci telah dihapus jauh lebih efisien daripada dikt yang baru dibuat dengan item yang dipertahankan.
Apalala
14
agar terbaca, saya sarankan {k: v untuk k, v in t.items () if k not in [key1, key2]}
Frederic Bazin
8
Ini juga memiliki masalah kinerja ketika daftar kunci terlalu besar, seperti yang dilakukan pencarian O(n). Seluruh operasi adalah O(mn), di mana mjumlah kunci di dikt dan njumlah kunci di daftar. Saya sarankan menggunakan satu set {key1, key2}, jika memungkinkan.
ldavid
4
Kepada Apalala: dapatkah Anda membantu saya memahami mengapa ada pertunjukan yang sukses?
Sean
21

solusi menggunakan mapdan filterfungsi

python 2

d={"a":1,"b":2,"c":3}
l=("a","b","d")
map(d.__delitem__, filter(d.__contains__,l))
print(d)

python 3

d={"a":1,"b":2,"c":3}
l=("a","b","d")
list(map(d.__delitem__, filter(d.__contains__,l)))
print(d)

Anda mendapatkan:

{'c': 3}
Jose Ricardo Bustos M.
sumber
Ini tidak berhasil untuk saya dengan python 3.4:>>> d={"a":1,"b":2,"c":3} >>> l=("a","b","d") >>> map(d.__delitem__, filter(d.__contains__,l)) <map object at 0x10579b9e8> >>> print(d) {'a': 1, 'b': 2, 'c': 3}
Risadinha
@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... di python 3.4 fungsi peta mengembalikan iterator
Jose Ricardo Bustos M.
4
atau deque(map(...), maxlen=0)untuk menghindari membangun daftar nilai Tidak Ada; impor pertama denganfrom collections import deque
Jason
19

Jika Anda juga perlu mengambil nilai untuk kunci yang Anda hapus, ini akan menjadi cara yang cukup baik untuk melakukannya:

valuesRemoved = [d.pop(k, None) for k in entitiesToRemove]

Anda tentu saja masih dapat melakukan ini hanya untuk menghapus kunci dari d, tetapi Anda tidak perlu membuat daftar nilai dengan pemahaman daftar. Juga agak tidak jelas untuk menggunakan pemahaman daftar hanya untuk efek samping fungsi.

Andrew Clark
sumber
3
Atau jika Anda ingin menyimpan entri yang dihapus sebagai kamus: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) dan seterusnya.
kindall
Anda dapat meninggalkan tugas ke variabel. Dengan cara ini atau itu, ini adalah solusi terpendek dan paling pythonic dan harus ditandai sebagai jawaban yang benar IMHO.
Gerhard Hagerer
12

Menemukan solusi dengan popdanmap

d = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'b', 'c']
list(map(d.pop, keys))
print(d)

Output dari ini:

{'d': 'valueD'}

Saya telah menjawab pertanyaan ini sangat terlambat hanya karena saya pikir akan membantu di masa depan jika ada yang mencari hal yang sama. Dan ini mungkin bisa membantu.

Memperbarui

Kode di atas akan memunculkan kesalahan jika kunci tidak ada di dict.

DICTIONARY = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'l', 'c']

def remove_keys(key):
    try:
        DICTIONARY.pop(key, None)
    except:
        pass  # or do any action

list(map(remove_key, keys))
print(DICTIONARY)

keluaran:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Shubham Srivastava
sumber
1
Jawaban ini akan memunculkan pengecualian jika tidak ada kunci keysdalam d- Anda harus memfilternya terlebih dahulu.
ingofreyer
@ingofreyer memperbarui kode untuk penanganan pengecualian. Terima kasih telah menemukan masalah ini. Saya pikir sekarang ini akan berhasil. :)
Shubham Srivastava
Terima kasih, ini akan membantu semua orang menemukan jawaban ini :-)
ingofreyer
Membuat daftar sebagai produk sampingan dari penggunaan peta, membuatnya cukup lambat, sebenarnya lebih baik untuk mengulanginya.
Charlie Clark
4

Saya tidak memiliki masalah dengan jawaban yang ada, tetapi saya terkejut tidak menemukan solusi ini:

keys_to_remove = ['a', 'b', 'c']
my_dict = {k: v for k, v in zip("a b c d e f g".split(' '), [0, 1, 2, 3, 4, 5, 6])}

for k in keys_to_remove:
    try:
        del my_dict[k]
    except KeyError:
        pass

assert my_dict == {'d': 3, 'e': 4, 'f': 5, 'g': 6}

Catatan: Saya menemukan pertanyaan ini datang dari sini . Dan jawaban saya terkait dengan jawaban ini .

Doug R.
sumber
3

Kenapa tidak:

entriestoremove = (2,5,1)
for e in entriestoremove:
    if d.has_key(e):
        del d[e]

Saya tidak tahu apa yang Anda maksud dengan "cara yang lebih cerdas". Tentunya ada cara lain, mungkin dengan pemahaman kamus:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viathan
sumber
2

Di barisan

import functools

#: not key(c) in d
d = {"a": "avalue", "b": "bvalue", "d": "dvalue"}

entitiesToREmove = ('a', 'b', 'c')

#: python2
map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove)

#: python3

list(map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove))

print(d)
# output: {'d': 'dvalue'}
chuang wang
sumber
2

Beberapa tes waktu untuk cpython 3 menunjukkan bahwa perulangan for yang sederhana adalah cara tercepat, dan cukup mudah dibaca. Menambahkan fungsi juga tidak menyebabkan banyak overhead:

hasil timeit (10k iterasi):

  • all(x.pop(v) for v in r) # 0.85
  • all(map(x.pop, r)) # 0.60
  • list(map(x.pop, r)) # 0.70
  • all(map(x.__delitem__, r)) # 0.44
  • del_all(x, r) # 0.40
  • <inline for loop>(x, r) # 0.35
def del_all(mapping, to_remove):
      """Remove list of elements from mapping."""
      for key in to_remove:
          del mapping[key]

Untuk iterasi kecil, melakukan 'inline' itu sedikit lebih cepat, karena overhead pemanggilan fungsi. Tapi del_allaman untuk lint, dapat digunakan kembali, dan lebih cepat dari semua pemahaman python dan konstruksi pemetaan.

Erik Aronesty
sumber
0

Saya pikir menggunakan fakta bahwa kunci dapat diperlakukan sebagai satu set adalah cara terbaik jika Anda menggunakan python 3:

def remove_keys(d, keys):
    to_remove = set(keys)
    filtered_keys = d.keys() - to_remove
    filtered_values = map(d.get, filtered_keys)
    return dict(zip(filtered_keys, filtered_values))

Contoh:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}
Reut Sharabani
sumber
0

Alangkah baiknya memiliki dukungan penuh untuk metode set untuk kamus (dan bukan kekacauan tidak suci yang kita dapatkan dengan Python 3.9) sehingga Anda bisa "menghapus" satu set kunci. Namun, selama bukan itu masalahnya, dan Anda memiliki kamus besar dengan kemungkinan sejumlah besar kunci untuk dihapus, Anda mungkin ingin tahu tentang kinerjanya. Jadi, saya telah membuat beberapa kode yang membuat sesuatu yang cukup besar untuk perbandingan yang bermakna: matriks 100.000 x 1000, jadi total 10.000,00 item.

from itertools import product
from time import perf_counter

# make a complete worksheet 100000 * 1000
start = perf_counter()
prod = product(range(1, 100000), range(1, 1000))
cells = {(x,y):x for x,y in prod}
print(len(cells))

print(f"Create time {perf_counter()-start:.2f}s")
clock = perf_counter()
# remove everything above row 50,000

keys = product(range(50000, 100000), range(1, 100))

# for x,y in keys:
#     del cells[x, y]

for n in map(cells.pop, keys):
    pass

print(len(cells))
stop = perf_counter()
print(f"Removal time {stop-clock:.2f}s")

10 juta item atau lebih bukanlah hal yang aneh di beberapa pengaturan. Membandingkan dua metode pada mesin lokal saya, saya melihat sedikit peningkatan saat menggunakan mapdan pop, mungkin karena panggilan fungsi yang lebih sedikit, tetapi keduanya memakan waktu sekitar 2,5 detik pada mesin saya. Tapi ini artinya jika dibandingkan dengan waktu yang dibutuhkan untuk membuat kamus di tempat pertama (55-an), atau termasuk pemeriksaan dalam loop. Jika ini mungkin terjadi, sebaiknya buat set yang merupakan perpotongan dari kunci kamus dan filter Anda:

keys = cells.keys() & keys

Singkatnya: delsudah sangat dioptimalkan, jadi jangan khawatir tentang menggunakannya.

Charlie Clark
sumber
-1

Saya terlambat untuk diskusi ini tetapi untuk orang lain. Solusinya mungkin dengan membuat daftar kunci seperti itu.

k = ['a','b','c','d']

Kemudian gunakan pop () dalam pemahaman daftar, atau for loop, untuk mengulang tombol dan pop satu per satu.

new_dictionary = [dictionary.pop(x, 'n/a') for x in k]

'N / a' adalah jika kunci tidak ada, nilai default perlu dikembalikan.

Terrance DeJesus
sumber
8
new_dictionaryterlihat sangat banyak seperti daftar;)
DylanYoung