Pertanyaan Bagaimana cara menggabungkan dua kamus dalam satu ekspresi?


Saya memiliki dua kamus Python, dan saya ingin menulis satu ekspresi yang mengembalikan dua kamus ini, digabungkan. Itu update() metode akan menjadi apa yang saya butuhkan, jika mengembalikan hasilnya bukan memodifikasi dict di tempat.

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

Bagaimana saya bisa mendapatkan digabung final itu ztidak x?

(Untuk menjadi sangat jelas, yang terakhir-menang-konflik penanganan dict.update() adalah apa yang saya cari juga.)


3211
2017-09-02 07:44


asal


Jawaban:


Bagaimana saya bisa menggabungkan dua kamus Python dalam satu ekspresi?

Untuk kamus x dan y, z menjadi kamus gabungan dengan nilai dari y menggantikan mereka dari x.

  • Dengan Python 3,5 atau lebih besar,:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • Dalam Python 2, (atau 3.4 atau lebih rendah) tulis suatu 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

    z = merge_two_dicts(x, y)
    

Penjelasan

Katakanlah Anda memiliki dua dicts dan Anda ingin menggabungkannya menjadi dikt 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 yang digabungkan, dan nilai-nilai kedua dict itu 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, aku s

z = {**x, **y}

Dan itu memang ekspresi tunggal. Ini sekarang ditampilkan seperti yang diterapkan di jadwal rilis untuk 3,5, PEP 478, dan sekarang sudah masuk Apa yang Baru di Python 3.5 dokumen.

Namun, karena banyak organisasi masih menggunakan Python 2, Anda mungkin ingin melakukan ini dengan cara yang kompatibel ke belakang. Cara Pythonic klasik, 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 itu, y akan datang kedua dan nilainya akan menggantikan xnilai-nilai, dengan demikian 'b' akan menunjuk ke 3 dalam hasil akhir kami.

Belum pada Python 3.5, tetapi menginginkan a ekspresi tunggal

Jika Anda belum menggunakan Python 3.5, atau perlu menulis kode yang kompatibel dengan ke belakang, dan Anda ingin ini di a ekspresi tunggal, yang paling berkinerja sementara 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

lalu Anda memiliki satu ekspresi:

z = merge_two_dicts(x, y)

Anda juga dapat membuat fungsi untuk menggabungkan sejumlah tak terhingga dari nol, dari nol ke angka 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 bekerja dengan Python 2 dan 3 untuk semua dicts. misalnya diberi dicts a untuk g:

z = merge_dicts(a, b, c, d, e, f, g) 

dan pasangan nilai kunci dalam g akan didahulukan daripada dicts a untuk f, dan seterusnya.

Kritik dari 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 dalam memori dengan panjang yang sama dengan panjang dua yang pertama disatukan, dan kemudian membuang ketiga daftar untuk membuat dict. Dengan Python 3, ini akan gagal karena Anda menambahkan dua dict_items benda-benda bersama, 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 secara eksplisit membuatnya sebagai daftar, mis. z = dict(list(x.items()) + list(y.items())). Ini adalah pemborosan sumber daya dan daya komputasi.

Demikian pula, mengambil penyatuan items()dengan Python 3 (viewitems() dengan Python 2.7) juga akan gagal ketika nilai adalah objek yang tidak dapat diraba (seperti daftar, misalnya). Bahkan jika nilai Anda dapat diubah, karena set secara semantik tidak berurutan, perilaku tidak terdefinisi dalam hal yang didahulukan. 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 ini contoh di mana y harus didahulukan, tetapi nilai dari x dipertahankan karena urutan set yang berubah-ubah:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

Peretasan lain yang seharusnya tidak Anda gunakan:

z = dict(x, **y)

Ini menggunakan dict konstruktor, dan sangat cepat dan efisien memori (bahkan sedikit lebih-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 konstruktor dikt), sulit untuk membaca, itu bukan penggunaan yang dimaksudkan, dan jadi itu tidak Pythonic.

Berikut contoh penggunaannya diremediasi dalam Django.

Dict dimaksudkan untuk mengambil kunci hashable (misalnya frozensets atau tuples), tetapi metode ini gagal dengan 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 baik-baik saja dengan   menyatakan dict ({}, ** {1: 3}) ilegal, karena bagaimanapun itu adalah penyalahgunaan   mekanisme **.

dan

Rupanya dict (x, ** y) terjadi di sekitar sebagai "hack keren" untuk "panggilan   x.update (y) dan kembali x ". Secara pribadi saya merasa lebih tercela daripada   keren.

Itu adalah pemahaman saya (serta pemahaman tentang pencipta bahasa) bahwa penggunaan yang dimaksudkan untuk dict(**y) adalah untuk membuat dicts untuk keperluan keterbacaan, misalnya:

dict(a=1, b=10, c=11)

dari pada

{'a': 1, 'b': 10, 'c': 11}

Tanggapan untuk komentar

Terlepas dari apa yang dikatakan Guido, dict(x, **y) sesuai dengan spesifikasi dict, yang btw. bekerja untuk Python 2 dan 3. Fakta bahwa ini hanya berfungsi untuk kunci string adalah konsekuensi langsung dari cara kerja parameter kata kunci dan bukan dikt yang pendek. Tidak juga menggunakan operator ** di tempat ini penyalahgunaan mekanisme, sebenarnya ** dirancang tepat untuk memberikan perintah sebagai kata kunci.

Sekali lagi, itu tidak berhasil untuk 3 ketika kunci tidak string. Kontrak panggilan implisit adalah bahwa ruang nama mengambil pengandaian biasa, sementara pengguna hanya harus meneruskan argumen kata kunci yang merupakan string. Semua yang bisa ditegakkan lainnya diberlakukan. dict memecahkan konsistensi ini dengan 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 diberikan implementasi lain dari Python (Pypy, Jython, IronPython). Jadi itu diperbaiki dengan Python 3, karena penggunaan ini bisa menjadi perubahan yang melanggar.

Saya menyampaikan kepada Anda bahwa itu adalah ketidakmampuan yang berbahaya untuk secara sengaja menulis kode yang hanya bekerja dalam satu versi bahasa atau yang hanya berfungsi dengan batasan tertentu yang sewenang-wenang.

Komentar lain:

dict(x.items() + y.items()) masih merupakan solusi yang paling mudah dibaca untuk Python 2. Keterbacaan readability.

Tanggapan saya: merge_two_dicts(x, y) sebenarnya tampak lebih jelas bagi saya, jika kami benar-benar khawatir tentang keterbacaan. Dan tidak kompatibel ke depan, karena Python 2 semakin usang.

Performing Kurang Tapi Benar Ad-hocs

Pendekatan ini kurang berkinerja, tetapi mereka akan memberikan perilaku yang benar. Mereka akan apalagi lebih baik daripada copy dan update atau pembongkaran baru karena mereka iterasi melalui setiap pasangan nilai kunci pada tingkat abstraksi yang lebih tinggi, tetapi mereka melakukan menghormati urutan diutamakan (yang disebutkan di atas memiliki preseden)

Anda juga dapat merantai 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 di 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 iterators atas pasangan kunci-nilai 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 dengan benar.

import timeit

Hal 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 daya di Kamus


3336
2017-11-10 22:11



Dalam kasus Anda, yang dapat Anda lakukan adalah:

z = dict(x.items() + y.items())

Ini akan, seperti yang Anda inginkan, menempatkan dict final z, dan buat nilai untuk kunci b benar ditimpa oleh yang kedua (y) Nilai dict's:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Jika Anda menggunakan Python 3, itu hanya sedikit lebih rumit. Untuk membuat z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

1438
2017-09-02 07:50



Sebuah alternatif:

z = x.copy()
z.update(y)

546
2017-09-02 13:00



Pilihan lain, lebih ringkas,:

z = dict(x, **y)

Catatan: ini telah menjadi jawaban yang populer, tetapi penting untuk menunjukkan bahwa jika y memiliki kunci non-string, fakta bahwa ini bekerja sama sekali merupakan penyalahgunaan dari detail implementasi CPython, dan itu tidak bekerja di Python 3, atau di PyPy, IronPython, atau Jython. Juga, Guido bukan penggemar. Jadi saya tidak dapat merekomendasikan teknik ini untuk kode portabel yang kompatibel ke depan atau lintas implementasi, yang berarti itu harus dihindari sepenuhnya.


272
2017-09-02 15:52



Ini mungkin tidak akan menjadi jawaban yang populer, tetapi Anda hampir pasti tidak ingin melakukan ini. Jika Anda ingin salinan yang digabungkan, lalu gunakan salin (atau deepcopy, tergantung pada apa yang Anda inginkan) dan kemudian perbarui. Dua baris kode jauh lebih mudah dibaca - lebih Pythonic - daripada pembuatan garis tunggal dengan .items () + .items (). Eksplisit lebih baik daripada implisit.

Selain itu, ketika Anda menggunakan .items () (pre Python 3.0), Anda membuat daftar baru yang berisi item dari dikt. Jika kamus Anda besar, maka itu cukup banyak di atas (dua daftar besar yang akan dibuang begitu dikodekan gabungan dibuat). update () dapat bekerja lebih efisien, karena dapat dijalankan melalui item kedua di-by-item.

Dengan kondisi waktu:

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO pelambatan kecil antara dua yang pertama sangat berharga untuk pembacaan. Selain itu, argumen kata kunci untuk pembuatan kamus hanya ditambahkan dengan Python 2.3, sedangkan copy () dan update () akan berfungsi dalam versi yang lebih lama.


167
2017-09-08 11:16



Dalam jawaban tindak lanjut, Anda bertanya tentang kinerja relatif dari dua alternatif ini:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

Di mesin saya, setidaknya (x86_64 yang cukup biasa menjalankan Python 2.5.2), alternatif z2 tidak hanya lebih pendek dan lebih sederhana tetapi juga secara signifikan lebih cepat. Anda dapat memverifikasi ini untuk diri sendiri menggunakan timeit modul yang dilengkapi dengan Python.

Contoh 1: kamus identik memetakan 20 bilangan bulat berturut-turut untuk diri mereka sendiri:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2 menang dengan faktor 3,5 atau lebih. Kamus yang berbeda tampaknya menghasilkan hasil yang sangat berbeda, tetapi z2 sepertinya selalu muncul ke depan. (Jika Anda mendapatkan hasil yang tidak konsisten untuk sama uji coba, coba lewat -r dengan angka yang lebih besar dari default 3.)

Contoh 2: pemetaan kamus yang tidak tumpang tindih 252 string pendek ke bilangan bulat dan sebaliknya:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 menang sekitar 10 kali. Itu kemenangan besar dalam buku saya!

Setelah membandingkan keduanya, saya bertanya-tanya apakah z1Kinerja yang buruk dapat dikaitkan dengan overhead membangun dua daftar item, yang pada gilirannya membuat saya bertanya-tanya apakah variasi ini mungkin bekerja lebih baik:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

Beberapa tes cepat, mis.

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

menuntun saya untuk menyimpulkan itu z3 agak lebih cepat dari z1, tetapi tidak secepat secepat itu z2. Jelas tidak layak untuk semua pengetikan ekstra.

Diskusi ini masih kehilangan sesuatu yang penting, yang merupakan perbandingan kinerja dari alternatif-alternatif ini dengan cara "jelas" untuk menggabungkan dua daftar: menggunakan update metode. Untuk mencoba menjaga hal-hal pada pijakan yang sama dengan ekspresi, tidak ada yang memodifikasi x atau y, saya akan membuat salinan x bukan memodifikasi di tempat, sebagai berikut:

z0 = dict(x)
z0.update(y)

Hasil yang khas:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

Dengan kata lain, z0 dan z2 tampaknya memiliki kinerja yang pada dasarnya identik. Apakah Anda pikir ini mungkin suatu kebetulan? Bukan saya....

Bahkan, saya akan mengklaim bahwa tidak mungkin kode Python murni untuk melakukan lebih baik dari ini. Dan jika Anda dapat melakukan secara signifikan lebih baik dalam modul ekstensi C, saya membayangkan orang-orang Python mungkin tertarik untuk memasukkan kode Anda (atau variasi pada pendekatan Anda) ke dalam inti Python. Python menggunakan dict di banyak tempat; mengoptimalkan operasinya adalah masalah besar.

Anda juga bisa menulis ini sebagai

z0 = x.copy()
z0.update(y)

seperti Tony, tetapi (tidak mengherankan) perbedaan dalam notasi ternyata tidak memiliki efek terukur pada kinerja. Gunakan mana yang terlihat tepat untuk Anda. Tentu saja, dia benar-benar tepat untuk menunjukkan bahwa versi dua pernyataan jauh lebih mudah dimengerti.


116
2017-10-23 02:38



Saya menginginkan sesuatu yang serupa, tetapi dengan kemampuan untuk menentukan bagaimana nilai pada kunci duplikat digabungkan, jadi saya meretasnya (tetapi tidak terlalu mengujinya). Jelas ini bukan ekspresi tunggal, tetapi itu adalah panggilan fungsi tunggal.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

86
2017-09-04 19:08



Dengan Python 3, Anda bisa menggunakannya koleksi .ChainMap yang mengelompokkan beberapa dict atau pemetaan lain bersama-sama untuk membuat tampilan tunggal yang dapat diperbarui:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = ChainMap({}, y, x)
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

73
2018-04-28 03:15