Filter dict hanya berisi kunci tertentu?


496

Saya punya dictyang memiliki banyak entri. Saya hanya tertarik pada beberapa dari mereka. Apakah ada cara mudah untuk memangkas yang lainnya?


Sangat membantu untuk mengatakan apa jenis kunci (bilangan bulat? String? Tanggal? Objek sewenang-wenang?) Dan dengan demikian apakah ada tes sederhana (string, regex, daftar keanggotaan, atau ketimpangan numerik) untuk memeriksa kunci mana yang masuk atau keluar. Atau kita perlu memanggil fungsi arbitrer untuk menentukan itu.
smci

@smci Kunci string. Jangan pernah berpikir bahwa saya bisa menggunakan yang lain; Saya sudah mengkode dalam JS dan PHP begitu lama ...
mpen

Jawaban:


656

Membangun dikte baru:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

Menggunakan pemahaman kamus.

Jika Anda menggunakan versi yang tidak memilikinya (yaitu Python 2.6 dan sebelumnya), buatlah dict((your_key, old_dict[your_key]) for ...). Itu sama, meskipun lebih jelek.

Perhatikan bahwa ini, tidak seperti versi jnnnnn, memiliki kinerja yang stabil (hanya bergantung pada jumlah tombol_Anda) untuk old_dictukuran berapa saja. Baik dari segi kecepatan maupun memori. Karena ini adalah ekspresi generator, ia memproses satu item pada satu waktu, dan itu tidak melihat semua item old_dict.

Menghapus semua yang ada di tempat:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]

8
"Menggunakan pemahaman kamus, jika Anda menggunakan versi yang tidak memilikinya" == version <= 2.6
getekha

8
Melempar KeyError jika salah satu kunci filer tidak ada di old_dict. Saya akan menyarankan {k: d [k] untuk k dalam filter jika k dalam d}
Peter Gibson

1
@ PeterGibson Ya, jika itu bagian dari persyaratan, Anda perlu melakukan sesuatu tentang itu. Apakah itu menjatuhkan kunci secara diam-diam, menambahkan nilai default, atau sesuatu yang lain, tergantung pada apa yang Anda lakukan; ada banyak kasus penggunaan di mana pendekatan Anda salah. Ada juga banyak di mana kunci yang hilang old_dictmengindikasikan bug di tempat lain, dan dalam hal ini saya sangat suka kesalahan untuk hasil diam-diam salah.

@delnan, juga tambahan "if k in d" memperlambat Anda jika d besar, saya hanya berpikir itu layak disebut
Peter Gibson

7
@PeterGibson Tidak, pencarian kamus adalah O (1).

130

Pemahaman dikt sedikit lebih elegan:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}

Terpilih. Saya sedang berpikir tentang menambahkan jawaban yang mirip dengan ini. Hanya karena penasaran, mengapa {k: v untuk k, v di dict.items () ...} daripada {k: dict [k] untuk k di dict ...} Apakah ada perbedaan kinerja?
Hart Simha

4
Menjawab pertanyaan saya sendiri. {K: dict [k] untuk k di dict ...} sekitar 20-25% lebih cepat, setidaknya dalam Python 2.7.6, dengan kamus berisi 26 item (timeit (..., setup = "d = {chr (x + 97): x +1 untuk x dalam rentang (26)} ")), tergantung pada berapa banyak item yang disaring (menyaring kunci konsonan lebih cepat daripada menyaring kunci vokal karena Anda mencari lebih sedikit item). Perbedaan dalam kinerja mungkin menjadi kurang signifikan ketika ukuran kamus Anda bertambah.
Hart Simha

5
Mungkin perf yang sama jika Anda menggunakannya mydict.iteritems(). .items()membuat daftar lain.
Pat

64

Berikut ini contoh dalam python 2.6:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

Bagian penyaringan adalah ifpernyataan.

Metode ini lebih lambat daripada jawaban delnan jika Anda hanya ingin memilih beberapa kunci yang sangat banyak.


11
kecuali saya mungkin akan menggunakan if key in ('x','y','z')kurasa.
mpen

jika Anda sudah tahu kunci mana yang Anda inginkan, gunakan jawaban delnan. Jika Anda perlu menguji setiap kunci dengan pernyataan if, gunakan jawaban ransford.
jnnnnn

1
Solusi ini memiliki satu keunggulan lagi. Jika kamus dikembalikan dari panggilan fungsi yang mahal (yaitu a / old_dict adalah panggilan fungsi) solusi ini memanggil fungsi hanya sekali. Dalam lingkungan imperatif menyimpan kamus yang dikembalikan oleh fungsi dalam variabel bukan masalah besar tetapi dalam lingkungan fungsional (misalnya dalam lambda) ini adalah pengamatan utama.
gae123

21

Anda dapat melakukannya dengan proyek fungsi dari saya funcy perpustakaan:

from funcy import project
small_dict = project(big_dict, keys)

Lihat juga pada select_keys .


20

Kode 1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
    if key % 2 == 0:
        d1[key] = value

Kode 2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

Kode 3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

Semua bagian dari kinerja kode diukur dengan timeit menggunakan angka = 1000, dan dikumpulkan 1000 kali untuk setiap bagian kode.

masukkan deskripsi gambar di sini

Untuk python 3.6 kinerja tiga cara kunci dict filter hampir sama. Untuk python 2.7 kode 3 sedikit lebih cepat.


hanya ingin tahu, apakah Anda membuat plot itu dari Python?
user5359531

1
ggplot2 di R - bagian dari tidyverse
keithpjolley

18

Lambda satu liner ini harus bekerja:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

Ini sebuah contoh:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")

# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

Ini adalah pemahaman daftar dasar yang berulang pada kunci dikte Anda (i in x) dan menampilkan daftar pasangan tuple (kunci, nilai) jika kunci tersebut tinggal dalam daftar kunci yang Anda inginkan (y). Dict () membungkus semuanya menjadi output sebagai objek dict.


Harus menggunakan setuntuk wanted_keys, tetapi sebaliknya terlihat bagus.
mpen

Ini memberi saya kamus kosong jika kamus asli saya berisi daftar menggantikan nilai. Ada solusi?
FaCoffee

@ Francesco, dapatkah Anda memberikan contoh? Jika saya menjalankan dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')):, ia kembali {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}seperti yang dimaksudkan.
Jim

Saya mencoba ini dengan: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}dan hasilnya adalah {}, yang saya anggap sebagai dict kosong.
FaCoffee

Satu hal, "dict" adalah kata yang dilindungi undang-undang sehingga Anda tidak boleh menggunakannya untuk memberi nama dict. Apa kunci yang ingin Anda tarik? Jika saya menjalankan:, foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2'))saya mendapatkan: {'0': [1, 3], '2': [1, 4]}yang merupakan hasil yang diinginkan
Jim

14

Diberikan kamus asli Anda origdan sekumpulan entri yang Anda minati keys:

filtered = dict(zip(keys, [orig[k] for k in keys]))

yang tidak sebaik jawaban delnan, tetapi harus bekerja di setiap versi Python yang menarik. Namun, ini rapuh untuk setiap elemen yang keysada di kamus asli Anda.


Nah, ini pada dasarnya adalah versi "tuple generator versi" dari pemahaman dict saya. Sangat cocok memang, meskipun ekspresi generator diperkenalkan pada 2.4, musim semi 2005 - serius, apakah ada yang masih menggunakan ini?

1
Saya tidak setuju; 2.3 seharusnya tidak ada lagi. Namun, sebagai survei lama tentang penggunaan 2.3: moinmo.in/PollAboutRequiringPython24 Versi singkat: RHEL4, SLES9, dikirimkan dengan OS X 10.4
Kai

7

Berdasarkan jawaban yang diterima oleh delnan.

Bagaimana jika salah satu kunci yang Anda inginkan tidak ada di old_dict? Solusi delnan akan melempar pengecualian KeyError yang dapat Anda tangkap. Jika bukan itu yang Anda butuhkan, mungkin Anda ingin:

  1. hanya sertakan kunci yang ada di old_dict dan kumpulan want_keys Anda.

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    >>> new_dict
    {'name': 'Foobar'}
  2. memiliki nilai default untuk kunci yang tidak disetel di old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}

Anda juga bisa melakukannya{k: old_dict.get(k, default) for k in ...}
Moberg

6

Fungsi ini akan melakukan trik:

def include_keys(dictionary, keys):
    """Filters a dict by only including certain keys."""
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}

Sama seperti versi delnan, kamus ini menggunakan pemahaman kamus dan memiliki kinerja yang stabil untuk kamus besar (hanya bergantung pada jumlah kunci yang Anda izinkan, dan bukan jumlah total kunci dalam kamus).

Dan seperti versi MyGGan, yang ini memungkinkan daftar kunci Anda untuk memasukkan kunci yang mungkin tidak ada dalam kamus.

Dan sebagai bonus, inilah kebalikannya, di mana Anda dapat membuat kamus dengan mengecualikan kunci tertentu di aslinya:

def exclude_keys(dictionary, keys):
    """Filters a dict by excluding certain keys."""
    key_set = set(dictionary.keys()) - set(keys)
    return {key: dictionary[key] for key in key_set}

Perhatikan bahwa tidak seperti versi delnan, operasi tidak dilakukan di tempat, sehingga kinerjanya terkait dengan jumlah kunci dalam kamus. Namun, keuntungannya adalah bahwa fungsi ini tidak akan mengubah kamus yang disediakan.

Sunting: Menambahkan fungsi terpisah untuk mengecualikan kunci tertentu dari dikt.


Anda harus mengizinkan keysdengan segala jenis iterable, seperti set apa yang diterima.
mpen

Ah, panggilan yang bagus, terima kasih telah menunjukkan ini. Saya akan membuat pembaruan itu.
Ryan

Saya ingin tahu apakah Anda lebih baik dengan dua fungsi. Jika Anda bertanya kepada 10 orang "apakah invertmenyiratkan bahwa keysargumen itu dipertahankan, atau bahwa keysargumen itu ditolak?", Berapa banyak dari mereka akan setuju?
skatenerd

Diperbarui. Biarkan aku tahu apa yang Anda pikirkan.
Ryan

Ini tampaknya tidak berfungsi jika dict input memiliki daftar di tempat nilai. Dalam hal ini Anda mendapatkan dict batal. Ada solusi?
FaCoffee

4

Jika kita ingin membuat kamus baru dengan kunci yang dipilih dihapus, kita dapat menggunakan pemahaman kamus
Misalnya:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}

Rapi. Hanya berfungsi di Python 3. Python 2 mengatakan "TypeError: jenis operan yang tidak didukung untuk -: 'daftar' dan 'set'"
mpen

Menambahkan set (d.keys ()) untuk Python 2. Ini berfungsi saat saya menjalankan.
Srivastava

2

Pilihan lain:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

Tapi Anda mendapatkan list(Python 2) atau iterator (Python 3) dikembalikan oleh filter(), bukan a dict.


Bungkus filtereddi dictdan Anda mendapatkan kembali kamus!
CMCDragonkai

1

Bentuk pendek:

[s.pop(k) for k in list(s.keys()) if k not in keep]

Seperti sebagian besar jawaban menyarankan untuk menjaga keringkasan kita harus membuat objek duplikat baik itu a listatau dict. Yang ini membuat membuang-buang listtetapi menghapus kunci aslinya dict.


0

Berikut adalah metode sederhana lain yang digunakan deldalam satu liner:

for key in e_keys: del your_dict[key]

e_keysadalah daftar kunci yang akan dikecualikan. Ini akan memperbarui dict Anda daripada memberi Anda yang baru.

Jika Anda ingin keluaran baru, buat salinan salinan sebelum menghapus:

new_dict = your_dict.copy()           #Making copy of dict

for key in e_keys: del new_dict[key]

0

Anda dapat menggunakan python-benedict, ini adalah subclass dict.

Instalasi: pip install python-benedict

from benedict import benedict

dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

Ini open-source di GitHub: https://github.com/fabiocaccamo/python-benedict


Penafian: Saya penulis perpustakaan ini.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.