Bagaimana saya bisa menggabungkan dua kamus Python dalam satu ekspresi?
Untuk kamus x
dan y
, z
jadilah kamus yang dangkal digabung dengan nilai y
menggantikannya x
.
Dalam Python 3.5 atau lebih tinggi:
z = {**x, **y}
Dalam Python 2, (atau 3,4 atau lebih rendah) tulis fungsi:
def merge_two_dicts(x, y):
z = x.copy() # start with x's keys and values
z.update(y) # modifies z with y's keys and values & returns None
return z
dan sekarang:
z = merge_two_dicts(x, y)
Dalam Python 3.9.0a4 atau lebih tinggi (tanggal rilis akhir kira-kira Oktober 2020): PEP-584 , dibahas di sini , diimplementasikan untuk lebih menyederhanakan ini:
z = x | y # NOTE: 3.9+ ONLY
Penjelasan
Katakanlah Anda memiliki dua dicts dan Anda ingin menggabungkannya menjadi dict baru tanpa mengubah dicts asli:
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
Hasil yang diinginkan adalah untuk mendapatkan kamus baru ( z
) dengan nilai-nilai digabungkan, dan nilai-nilai dict kedua menimpa mereka dari yang pertama.
>>> z
{'a': 1, 'b': 3, 'c': 4}
Sintaks baru untuk ini, diusulkan dalam PEP 448 dan tersedia pada Python 3.5 , adalah
z = {**x, **y}
Dan itu memang satu ekspresi.
Perhatikan bahwa kita juga dapat bergabung dengan notasi literal:
z = {**x, 'foo': 1, 'bar': 2, **y}
dan sekarang:
>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}
Sekarang ditampilkan sebagaimana diimplementasikan dalam jadwal rilis untuk 3,5, PEP 478 , dan sekarang telah membuat jalannya ke Apa yang Baru dalam dokumen Python 3.5 .
Namun, karena banyak organisasi masih menggunakan Python 2, Anda mungkin ingin melakukan ini dengan cara yang kompatibel. Cara klasik Pythonic, tersedia dalam Python 2 dan Python 3.0-3.4, adalah melakukan ini sebagai proses dua langkah:
z = x.copy()
z.update(y) # which returns None since it mutates z
Dalam kedua pendekatan, y
akan menjadi yang kedua dan nilainya akan menggantikan x
nilai-nilai, dengan demikian 'b'
akan menunjuk 3
pada hasil akhir kami.
Belum menggunakan Python 3.5, tetapi ingin satu ekspresi
Jika Anda belum menggunakan Python 3.5, atau perlu menulis kode yang kompatibel dengan backward, dan Anda ingin ini dalam satu ekspresi , yang paling performan ketika pendekatan yang benar adalah memasukkannya ke dalam fungsi:
def merge_two_dicts(x, y):
"""Given two dicts, merge them into a new dict as a shallow copy."""
z = x.copy()
z.update(y)
return z
dan kemudian Anda memiliki satu ekspresi:
z = merge_two_dicts(x, y)
Anda juga dapat membuat fungsi untuk menggabungkan jumlah dicts yang tidak ditentukan, dari nol ke jumlah yang sangat besar:
def merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
Fungsi ini akan berfungsi dalam Python 2 dan 3 untuk semua dicts. mis. dikte yang diberikan a
kepada g
:
z = merge_dicts(a, b, c, d, e, f, g)
dan pasangan nilai kunci g
akan diutamakan daripada dicts a
untuk f
, dan sebagainya.
Kritik atas Jawaban Lain
Jangan gunakan apa yang Anda lihat di jawaban yang sebelumnya diterima:
z = dict(x.items() + y.items())
Dalam Python 2, Anda membuat dua daftar dalam memori untuk setiap dict, membuat daftar ketiga di memori dengan panjang yang sama dengan panjang dari dua yang pertama disatukan, dan kemudian membuang ketiga daftar untuk membuat dict. Dalam Python 3, ini akan gagal karena Anda menambahkan dua dict_items
objek bersamaan, bukan dua daftar -
>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'
dan Anda harus membuatnya secara eksplisit sebagai daftar, misalnya z = dict(list(x.items()) + list(y.items()))
. Ini adalah pemborosan sumber daya dan daya komputasi.
Demikian pula, mengambil penyatuan items()
dalam Python 3 ( viewitems()
dalam Python 2.7) juga akan gagal ketika nilai-nilai adalah objek yang tidak dapat hancur (seperti daftar, misalnya). Bahkan jika nilai-nilai Anda dapat di hashable, karena set secara semantik tidak tertata, perilaku tidak terdefinisi sehubungan dengan diutamakan. Jadi jangan lakukan ini:
>>> c = dict(a.items() | b.items())
Contoh ini menunjukkan apa yang terjadi ketika nilai tidak dapat dihancurkan:
>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Berikut adalah contoh di mana y harus didahulukan, tetapi nilai dari x dipertahankan karena urutan set arbitrer:
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}
Retasan lain yang tidak boleh Anda gunakan:
z = dict(x, **y)
Ini menggunakan dict
konstruktor, dan sangat cepat dan efisien memori (bahkan sedikit lebih dari proses dua langkah kami), tetapi kecuali Anda tahu persis apa yang terjadi di sini (yaitu, dikt kedua dilewatkan sebagai argumen kata kunci ke dikt tersebut constructor), sulit dibaca, ini bukan penggunaan yang dimaksudkan, jadi bukan Pythonic.
Berikut adalah contoh penggunaan yang diperbaiki di Django .
Diktik dimaksudkan untuk mengambil kunci hashable (mis. Frozenset atau tuple), tetapi metode ini gagal dalam Python 3 ketika kunci bukan string.
>>> c = dict(a, **b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
Dari milis , Guido van Rossum, pencipta bahasa, menulis:
Saya setuju dengan menyatakan dict ({}, ** {1: 3}) ilegal, karena bagaimanapun itu adalah penyalahgunaan mekanisme **.
dan
Rupanya dict (x, ** y) digunakan sebagai "hack keren" untuk "panggil x.update (y) dan kembalikan x". Secara pribadi saya merasa lebih hina daripada keren.
Ini adalah pemahaman saya (serta pemahaman pencipta bahasa ) bahwa penggunaan yang dimaksudkan dict(**y)
adalah untuk membuat dicts untuk tujuan keterbacaan, misalnya:
dict(a=1, b=10, c=11)
dari pada
{'a': 1, 'b': 10, 'c': 11}
Menanggapi komentar
Terlepas dari apa yang dikatakan Guido, dict(x, **y)
ini sejalan dengan spesifikasi dict, yaitu btw. berfungsi untuk kedua Python 2 dan 3. Fakta bahwa ini hanya berfungsi untuk kunci string adalah konsekuensi langsung dari bagaimana parameter kata kunci bekerja dan bukan perintah singkat dari dict. Juga tidak menggunakan operator ** di tempat ini penyalahgunaan mekanisme, pada kenyataannya ** dirancang tepat untuk mengirimkan dikt sebagai kata kunci.
Sekali lagi, ini tidak berfungsi selama 3 ketika kunci non-string. Kontrak panggilan implisit adalah bahwa namespaces mengambil dicts biasa, sementara pengguna hanya harus melewati argumen kata kunci yang bersifat string. Semua callable lainnya memberlakukannya. dict
mematahkan konsistensi ini dalam Python 2:
>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}
Ketidakkonsistenan ini buruk mengingat implementasi lain dari Python (Pypy, Jython, IronPython). Jadi itu diperbaiki di Python 3, karena penggunaan ini bisa menjadi perubahan yang melanggar.
Saya serahkan kepada Anda bahwa adalah ketidakmampuan berbahaya untuk sengaja menulis kode yang hanya berfungsi dalam satu versi bahasa atau yang hanya berfungsi dengan batasan arbitrer tertentu.
Lebih banyak komentar:
dict(x.items() + y.items())
masih merupakan solusi yang paling mudah dibaca untuk Python 2. Jumlah keterbacaan.
Tanggapan saya: merge_two_dicts(x, y)
sebenarnya tampak jauh lebih jelas bagi saya, jika kita benar-benar khawatir tentang keterbacaan. Dan itu tidak kompatibel maju, karena Python 2 semakin usang.
{**x, **y}
sepertinya tidak menangani kamus bersarang. isi kunci bersarang hanya ditimpa, tidak digabungkan [...] Saya akhirnya dibakar oleh jawaban ini yang tidak bergabung secara rekursif dan saya terkejut tidak ada yang menyebutkannya. Dalam penafsiran saya tentang kata "menggabungkan" jawaban ini menggambarkan "memperbarui satu dict dengan yang lain", dan tidak menggabungkan.
Iya. Saya harus merujuk Anda kembali ke pertanyaan, yang meminta gabungan dua kamus yang dangkal , dengan nilai pertama ditimpa oleh yang kedua - dalam satu ekspresi.
Dengan asumsi dua kamus kamus, orang mungkin secara rekursif menggabungkannya dalam satu fungsi, tetapi Anda harus berhati-hati untuk tidak mengubah dikte dari sumber mana pun, dan cara paling pasti untuk menghindari itu adalah dengan membuat salinan ketika menetapkan nilai. Karena kunci harus hashable dan biasanya tidak dapat diubah, tidak ada gunanya menyalinnya:
from copy import deepcopy
def dict_of_dicts_merge(x, y):
z = {}
overlapping_keys = x.keys() & y.keys()
for key in overlapping_keys:
z[key] = dict_of_dicts_merge(x[key], y[key])
for key in x.keys() - overlapping_keys:
z[key] = deepcopy(x[key])
for key in y.keys() - overlapping_keys:
z[key] = deepcopy(y[key])
return z
Pemakaian:
>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}
Menghasilkan kontinjensi untuk tipe nilai lain jauh di luar cakupan pertanyaan ini, jadi saya akan mengarahkan Anda pada jawaban saya untuk pertanyaan kanonik pada "Kamus kamus digabung" .
Kurang Berkinerja Tapi Ad-hocs Benar
Pendekatan-pendekatan ini kurang berkinerja, tetapi mereka akan memberikan perilaku yang benar. Mereka akan jauh lebih performant dari copy
dan update
atau membongkar baru karena mereka iterate melalui setiap pasangan kunci-nilai pada tingkat yang lebih tinggi dari abstraksi, tetapi mereka melakukan menghormati urutan prioritas (dicts terakhir memiliki diutamakan)
Anda juga dapat rantai dicts secara manual di dalam pemahaman dict:
{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7
atau dalam python 2.6 (dan mungkin sedini 2.4 ketika ekspresi generator diperkenalkan):
dict((k, v) for d in dicts for k, v in d.items())
itertools.chain
akan rantai iterator di atas pasangan nilai kunci dalam urutan yang benar:
import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))
Analisis Kinerja
Saya hanya akan melakukan analisis kinerja penggunaan yang diketahui berperilaku benar.
import timeit
Berikut ini dilakukan pada Ubuntu 14.04
Dalam Python 2.7 (sistem Python):
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934
Dalam Python 3.5 (deadsnakes PPA):
>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287
Sumber pada Kamus
z = x | y