Bagaimana cara menggabungkan kamus bersama dengan Python?

91
d3 = dict(d1, **d2)

Saya mengerti bahwa ini menggabungkan kamus. Tapi, apakah itu unik? Bagaimana jika d1 memiliki kunci yang sama dengan d2 tetapi nilainya berbeda? Saya ingin d1 dan d2 digabungkan, tetapi d1 memiliki prioritas jika ada kunci duplikat.

TIMEX
sumber
9
Perlu diketahui bahwa trik ini dianggap sebagai penyalahgunaan **argumen kata kunci yang lewat kecuali semua kunci d2adalah string. Jika tidak semua kunci d2adalah string, ini gagal di Python 3.2, dan dalam implementasi alternatif Python seperti Jython, IronPython, dan PyPy. Lihat, misalnya, mail.python.org/pipermail/python-dev/2010-April/099459.html .
Mark Dickinson

Jawaban:

154

Anda dapat menggunakan .update()metode ini jika Anda tidak membutuhkan yang asli d2lagi:

Perbarui kamus dengan pasangan kunci / nilai dari yang lain, menimpa kunci yang ada . Kembali None.

Misalnya:

>>> d1 = {'a': 1, 'b': 2} 
>>> d2 = {'b': 1, 'c': 3}
>>> d2.update(d1)
>>> d2
{'a': 1, 'c': 3, 'b': 2}

Memperbarui:

Tentu saja Anda dapat menyalin kamus terlebih dahulu untuk membuat yang baru digabungkan. Ini mungkin atau mungkin tidak perlu. Jika Anda memiliki objek gabungan (objek yang berisi objek lain, seperti daftar atau instance kelas) dalam kamus Anda, copy.deepcopyjuga harus dipertimbangkan.

Felix Kling
sumber
1
Dengan kasus ini elemen d1 harus mendapatkan prioritas dengan benar jika kunci yang bertentangan ditemukan
Trey Hunner
Jika Anda masih membutuhkannya, buat saja salinannya. d3 = d2.copy () d3.update (d1) tapi saya ingin d1 + d2 ditambahkan ke bahasa.
stach
4
d1 + d2 bermasalah karena satu kamus harus memiliki prioritas selama konflik, dan yang mana tidak terlalu jelas.
rjh
d1 + d2 hanya akan diimplementasikan jika Python mendapatkan multimap, jika tidak, ambiguitas bagi pengguna terlalu membingungkan untuk penguatan pengetikan 8 byte.
Nick Bastin
Anda memiliki objek di kamus dalam contoh ini: isinstance(int, object) is Truenamun deepcopysepertinya tidak perlu.
Antony Hatchkins
43

Di Python2,

d1={'a':1,'b':2}
d2={'a':10,'c':3}

d1 menggantikan d2:

dict(d2,**d1)
# {'a': 1, 'c': 3, 'b': 2}

d2 menggantikan d1:

dict(d1,**d2)
# {'a': 10, 'c': 3, 'b': 2}

Perilaku ini bukan hanya kebetulan implementasi; itu dijamin dalam dokumentasi :

Jika kunci ditentukan baik dalam argumen posisi dan sebagai argumen kata kunci, nilai yang terkait dengan kata kunci tersebut dipertahankan dalam kamus.

unutbu
sumber
3
Contoh Anda akan gagal (menghasilkan TypeError) dengan Python 3.2, dan dalam versi Jython, PyPy dan IronPython saat ini: untuk versi Python tersebut, ketika meneruskan sebuah dict dengan **notasi, semua kunci dari dict itu harus berupa string. Lihat utas python-dev mulai dari mail.python.org/pipermail/python-dev/2010-April/099427.html untuk selengkapnya.
Mark Dickinson
@ Mark: Terima kasih atas perhatiannya. Saya telah mengedit kode agar kompatibel dengan implementasi non-CPython.
unutbu
3
gagal jika kunci Anda adalah tupel string dan angka. untuk mis. d1 = {(1, 'a'): 1, (1, 'b'): 0,} d2 = {(1, 'a'): 1, (2, 'b'): 2, (2, 'a'): 1,}
MySchizoBuddy
Mengenai sintaks pembongkaran, lihat posting ini untuk perubahan yang akan datang di python 3.5.
Ioannis Filippidis
Saya akan mengatakan itu d = dict(**d1, **d2)berhasil, tetapi itulah yang dirujuk @IoannisFilippidis dalam komentar mereka. Mungkin menyertakan cuplikan di sini akan lebih jelas, jadi ini dia.
dwanderson
14

Jika Anda ingin d1mendapat prioritas dalam konflik, lakukan:

d3 = d2.copy()
d3.update(d1)

Jika tidak, balikkan d2dan d1.

tzot.dll
sumber
1

Solusi saya adalah menentukan fungsi penggabungan . Itu tidak canggih dan hanya membutuhkan biaya satu baris. Berikut kode di Python 3.

from functools import reduce
from operator import or_

def merge(*dicts):
    return { k: reduce(lambda d, x: x.get(k, d), dicts, None) for k in reduce(or_, map(lambda x: x.keys(), dicts), set()) }

Tes

>>> d = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> d_letters = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d, d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d_letters, d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> merge(d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge()
{}

Ia bekerja untuk sejumlah argumen kamus. Jika ada kunci duplikat dalam kamus tersebut, kunci dari kamus paling kanan dalam daftar argumen menang.

Lei Zhao
sumber
1
Loop sederhana dengan .updatepanggilan di dalamnya ( merged={}diikuti oleh for d in dict: merged.update(d)) akan lebih pendek, lebih mudah dibaca dan lebih efisien.
Mark Dickinson
1
Atau jika Anda benar-benar ingin menggunakan reducedan lambda, bagaimana return reduce(lambda x, y: x.update(y) or x, dicts, {})?
Mark Dickinson
1
Anda dapat mencoba kode Anda di shell dan melihat apakah itu benar. Apa yang saya coba lakukan adalah menulis fungsi yang dapat mengambil berbagai argumen kamus dengan fungsi yang sama. Lebih baik tidak menggunakan x.update (y) di bawah lambda, karena selalu mengembalikan None . Dan saya mencoba untuk menulis fungsi merge_with yang lebih umum yang mengambil berbagai argumen kamus dan menangani kunci duplikat dengan fungsi yang disediakan. Setelah saya selesai, saya akan mempostingnya di utas lain yang solusinya lebih relevan.
Lei Zhao
Inilah tautan tempat saya menulis solusi yang lebih umum. Selamat datang dan lihatlah.
Lei Zhao
1

Dimulai Python 3.9, operator |membuat kamus baru dengan kunci dan nilai yang digabungkan dari dua kamus:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d3 = d2 | d1
# d3: {'b': 2, 'c': 3, 'a': 1}

Ini:

Membuat kamus baru d3 dengan kunci dan nilai gabungan d2 dan d1. Nilai d1 diprioritaskan saat d2 dan d1 berbagi kunci.


Perhatikan juga |=operator yang mengubah d2 dengan menggabungkan d1 in, dengan prioritas pada nilai d1:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d2 |= d1
# d2: {'b': 2, 'c': 3, 'a': 1}

Xavier Guihot
sumber
0

Saya percaya bahwa, seperti yang dinyatakan di atas, menggunakan d2.update(d1)adalah pendekatan terbaik dan Anda juga dapat menyalinnya d2terlebih dahulu jika masih membutuhkannya.

Meskipun, saya ingin menunjukkan bahwa dict(d1, **d2)sebenarnya cara yang buruk untuk menggabungkan kamus secara umum karena argumen kata kunci harus berupa string, sehingga akan gagal jika Anda memiliki dictseperti:

{
  1: 'foo',
  2: 'bar'
}
Olivier Melançon
sumber