Bagaimana cara menggabungkan dua kamus dalam satu ekspresi dengan Python?

4788

Saya memiliki dua kamus Python, dan saya ingin menulis satu ekspresi yang mengembalikan dua kamus ini, digabung. The update()Metode akan menjadi apa yang saya butuhkan, jika kembali hasilnya bukan memodifikasi kamus 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 kamus gabungan terakhir z, bukan x?

(Supaya lebih jelas, penanganan konflik yang terakhir menang dict.update()adalah apa yang saya cari juga.)

Carl Meyer
sumber
1
Jika Anda menggunakan Python 3.9 alpha, cukup gunakanz = x | y
The Daleks

Jawaban:

5708

Bagaimana saya bisa menggabungkan dua kamus Python dalam satu ekspresi?

Untuk kamus xdan y, zjadilah kamus yang dangkal digabung dengan nilai ymenggantikannya 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, yakan menjadi yang kedua dan nilainya akan menggantikan xnilai-nilai, dengan demikian 'b'akan menunjuk 3pada 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 akepada g:

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

dan pasangan nilai kunci gakan diutamakan daripada dicts auntuk 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_itemsobjek 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 dictkonstruktor, 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. dictmematahkan 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 copydan updateatau 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

Aaron Hall
sumber
9
@MohammadAzim "strings only" hanya berlaku untuk perluasan argumen kata kunci dalam callable, bukan sintaks pembongkaran umum. Untuk menunjukkan bahwa ini bekerja: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall
37
jawaban singkat seperti z = {**x, **y}benar - benar merangsang saya
pcko1
1
Ini dapat diubah ketika PEP-0584 diterima. Operator serikat pekerja baru akan diimplementasikan dengan sintaks berikut:x | y
Callam Delaney
2
Ketika jawaban membutuhkan ringkasan di bagian atas, itu terlalu panjang.
Gringo Suave
2
Hai, yang teratas adalah ringkasan, ya. Terserah kamu. Semuanya akan menjadi posting blog yang bagus. Catatan Py 3.4 dan di bawah ini adalah EOL, 3.5 mendekati EOL pada 2020-09.
Gringo Suave
1618

Dalam kasus Anda, yang dapat Anda lakukan adalah:

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

Ini akan, seperti yang Anda inginkan, masukkan dict terakhir z, dan membuat nilai untuk kunci bditimpa dengan benar oleh nilai ydict kedua ( ):

>>> 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}

Jika Anda menggunakan Python versi 3.9.0a4 atau lebih tinggi, maka Anda dapat langsung menggunakan:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)

Output: {'a': 1, 'c': 11, 'b': 10}
Thomas Vander Stichele
sumber
3
Jangan gunakan ini karena sangat tidak efisien. (Lihat hasil timeit di bawah ini.) Mungkin diperlukan di hari-hari Py2 jika fungsi wrapper bukan pilihan, tetapi hari-hari itu sekarang sudah lewat.
Gringo Suave
632

Sebuah alternatif:

z = x.copy()
z.update(y)
Matthew Schinckel
sumber
83
Untuk mengklarifikasi mengapa ini tidak memenuhi kriteria yang diberikan oleh pertanyaan: itu bukan ekspresi tunggal dan tidak kembali z.
Alex
2
@neuronet setiap oneliner biasanya hanya memindahkan kode yang harus terjadi ke komponen yang berbeda dan menyelesaikannya di sana. ini jelas salah satu kasusnya. tetapi bahasa lain memiliki konstruksi yang lebih bagus daripada python untuk ini. dan memiliki varian transparan referensial yang mengembalikan elemen itu bagus untuk dimiliki.
Alex
12
Begini: jika Anda perlu memberikan dua baris komentar yang menjelaskan satu baris kode Anda kepada orang yang Anda berikan kode ... apakah Anda benar-benar melakukannya dalam satu baris? :) Saya sepenuhnya setuju Python tidak baik untuk ini: harus ada cara yang lebih mudah. Meskipun jawaban ini lebih pythonic, apakah ini benar-benar eksplisit atau jelas? Updatebukan salah satu fungsi "inti" yang cenderung banyak digunakan orang.
eric
Nah, jika orang bersikeras menjadikannya oneliner, Anda selalu dapat melakukannya (lambda z: z.update(y) or z)(x.copy()): P
towr
341

Opsi lain yang lebih ringkas:

z = dict(x, **y)

Catatan : ini telah menjadi jawaban yang populer, tetapi penting untuk menunjukkan bahwa jika ymemiliki kunci non-string, fakta bahwa ini berfungsi sama sekali adalah penyalahgunaan detail implementasi CPython, dan itu tidak berfungsi dalam Python 3, atau dalam PyPy, IronPython, atau Jython. Juga, Guido bukan penggemar . Jadi saya tidak bisa merekomendasikan teknik ini untuk kode portabel maju-kompatibel atau lintas-implementasi, yang benar-benar berarti harus dihindari sepenuhnya.

Carl Meyer
sumber
Berfungsi dengan baik di Python 3 dan PyPy dan PyPy 3 , tidak dapat berbicara dengan Jython atau Iron. Mengingat pola ini secara eksplisit didokumentasikan (lihat bentuk konstruktor ketiga dalam dokumentasi ini) Saya berpendapat ini bukan "detail implementasi" tetapi penggunaan fitur yang disengaja.
amcgregor
5
@ amcgregor Anda melewatkan frasa kunci "jika Anda memiliki kunci non-string." Itu yang tidak berfungsi di Python3; fakta bahwa ia bekerja di CPython 2 adalah detail implementasi yang tidak bisa diandalkan. IFF semua kunci Anda dijamin berupa string, ini adalah opsi yang didukung penuh.
Carl Meyer
214

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

Selain itu, saat Anda menggunakan .items () (pre Python 3.0), Anda membuat daftar baru yang berisi item dari dikt. Jika kamus Anda besar, maka itu cukup banyak overhead (dua daftar besar yang akan dibuang segera setelah diktat gabungan dibuat). pembaruan () dapat bekerja lebih efisien, karena dapat dijalankan melalui item-demi-item kedua

Dalam hal 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 perlambatan kecil antara dua yang pertama adalah layak untuk dibaca. Selain itu, argumen kata kunci untuk pembuatan kamus hanya ditambahkan dalam Python 2.3, sedangkan salin () dan pembaruan () akan berfungsi di versi yang lebih lama.

Tony Meyer
sumber
150

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

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

Di komputer saya, setidaknya (x86_64 yang cukup biasa menjalankan Python 2.5.2), alternatif z2tidak hanya lebih pendek dan lebih sederhana tetapi juga secara signifikan lebih cepat. Anda dapat memverifikasi ini sendiri menggunakan timeitmodul yang disertakan 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

z2menang dengan faktor 3,5 atau lebih. Kamus-kamus yang berbeda nampaknya menghasilkan hasil yang sangat berbeda, tetapi z2sepertinya selalu keluar kedepan. (Jika Anda mendapatkan hasil yang tidak konsisten untuk pengujian yang sama , coba lewati -rdengan angka yang lebih besar dari standar 3.)

Contoh 2: kamus non-tumpang tindih memetakan 252 string pendek ke integer 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 faktor. Itu kemenangan yang cukup besar dalam buku saya!

Setelah membandingkan kedua, 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

arahkan saya untuk menyimpulkan bahwa z3ini agak lebih cepat daripada z1, tetapi tidak secepat secepat z2. Jelas tidak sepadan dengan semua pengetikan ekstra.

Diskusi ini masih kehilangan sesuatu yang penting, yang merupakan perbandingan kinerja dari alternatif ini dengan cara "jelas" menggabungkan dua daftar: menggunakan updatemetode ini. Untuk mencoba menjaga semuanya tetap sejajar dengan ekspresi, tidak ada yang memodifikasi x atau y, saya akan membuat salinan x alih-alih mengubahnya di tempat, sebagai berikut:

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

Hasil 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, z0dan z2tampaknya memiliki kinerja yang pada dasarnya identik. Apakah Anda pikir ini kebetulan? Bukan saya....

Bahkan, saya akan mengklaim bahwa tidak mungkin kode Python murni untuk melakukan yang lebih baik dari ini. Dan jika Anda dapat melakukan jauh 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. Penggunaan Python dictdi banyak tempat; mengoptimalkan operasinya adalah masalah besar.

Anda juga dapat menulis ini sebagai

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

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

zaphod
sumber
5
Ini tidak berfungsi di Python 3; items()tidak bisa dipatenkan, dan iteritemstidak ada.
Antti Haapala
127

Di Python 3.0 dan yang lebih baru , Anda bisa menggunakan collections.ChainMapgrup mana yang beberapa dicts atau pemetaan lainnya bersama-sama untuk membuat tampilan tunggal yang dapat diperbarui:

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

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

Pembaruan untuk Python 3.5 dan yang lebih baru : Anda dapat menggunakan pengemasan dan pembongkaran kamus PEP 448 yang diperluas. Ini cepat dan mudah:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
Raymond Hettinger
sumber
3
Tetapi orang harus berhati-hati saat menggunakan ChainMap ada tanda bahwa jika Anda memiliki kunci duplikat nilai-nilai dari pemetaan pertama digunakan dan ketika Anda memanggil a delmengatakan ChainMap c akan menghapus pemetaan pertama kunci itu.
Slayer
7
@Prerit Apa lagi yang Anda harapkan? Itu cara normal ruang nama dirantai bekerja. Pertimbangkan cara kerja $ PATH di bash. Menghapus executable di path tidak menghalangi executable lain dengan nama yang sama lebih jauh ke hulu.
Raymond Hettinger
2
@Raymond Hettinger Saya setuju, baru saja menambahkan peringatan. Kebanyakan orang mungkin tidak mengetahuinya. : D
Slayer
@Prerit Anda dapat memilih dictuntuk menghindarinya, yaitu:dict(ChainMap({}, y, x))
wjandrea
113

Saya menginginkan sesuatu yang serupa, tetapi dengan kemampuan untuk menentukan bagaimana nilai pada kunci duplikat digabungkan, jadi saya meretas ini (tapi tidak banyak mengujinya). Jelas ini bukan ekspresi tunggal, tetapi ini 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
rcreswick
sumber
88

Secara rekursif / mendalam perbarui dikt

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

Demonstrasi:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Output:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Terima kasih rednaw untuk pengeditan.

Stan
sumber
1
Ini tidak menjawab pertanyaan. Pertanyaannya dengan jelas meminta kamus baru, z, dari kamus asli, x dan y, dengan nilai dari y menggantikan kamus x - bukan kamus yang diperbarui. Jawaban ini memodifikasi y di tempat dengan menambahkan nilai dari x. Lebih buruk lagi, itu tidak menyalin nilai-nilai ini, sehingga orang dapat lebih lanjut memodifikasi kamus yang dimodifikasi, y, dan modifikasi dapat tercermin dalam kamus x. @ Jérôme Saya harap kode ini tidak menyebabkan bug untuk aplikasi Anda - setidaknya pertimbangkan untuk menggunakan deepcopy untuk menyalin nilai-nilai tersebut.
Aaron Hall
1
@ AaronHall setuju ini tidak menjawab pertanyaan. Tapi itu menjawab kebutuhan saya. Saya mengerti keterbatasan itu, tapi itu bukan masalah dalam kasus saya. Kalau dipikir-pikir, mungkin namanya menyesatkan, karena mungkin menimbulkan deepcopy, yang tidak disediakannya. Tapi itu alamat sarang yang dalam. Berikut implementasi lain dari Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme
72

Versi terbaik yang bisa saya pikirkan saat tidak menggunakan salinan adalah:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

Ini lebih cepat daripada dict(x.items() + y.items())tetapi tidak secepat n = copy(a); n.update(b), setidaknya pada CPython. Versi ini juga bekerja di Python 3 jika Anda mengubah iteritems()ke items(), yang secara otomatis dilakukan oleh alat 2to3.

Secara pribadi saya suka versi ini karena ini menggambarkan cukup baik apa yang saya inginkan dalam sintaks fungsional tunggal. Satu-satunya masalah kecil adalah bahwa tidak sepenuhnya jelas bahwa nilai-nilai dari y lebih diutamakan daripada nilai-nilai dari x, tapi saya tidak percaya sulit untuk mencari tahu itu.

driax
sumber
71

Python 3.5 (PEP 448) memungkinkan opsi sintaksis yang lebih bagus:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

Atau bahkan

final = {'a': 1, 'b': 1, **x, **y}

Dalam Python 3.9 Anda juga menggunakan | dan | = dengan contoh di bawah ini dari PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
Bilal Syed Hussain
sumber
Dalam hal apa solusi ini lebih baik daripada dict(x, **y)solusi? Seperti yang Anda (@CarlMeyer) sebutkan dalam catatan jawaban Anda sendiri ( stackoverflow.com/a/39858/2798610 ) Guido menganggap solusi itu ilegal .
Blackeagle52
14
Guido tidak suka dict(x, **y)karena alasan (sangat bagus) yang hanya bergantung pada ymemiliki kunci yang merupakan nama argumen kata kunci yang valid (kecuali jika Anda menggunakan CPython 2.7, di mana dict constructor menipu). Keberatan / pembatasan ini tidak berlaku untuk PEP 448, yang menggeneralisasikan **sintaks pembongkaran ke literct. Jadi solusi ini memiliki resolusi yang sama dengan dict(x, **y), tanpa kelemahan.
Carl Meyer
62
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

Untuk item dengan kunci di kedua kamus ('b'), Anda dapat mengontrol mana yang berakhir di output dengan menempatkan yang terakhir.

Greg Hewgill
sumber
Dalam python 3 Anda akan mendapatkan TypeError: jenis operan yang tidak didukung untuk +: 'dict_items' dan 'dict_items' ... Anda harus merangkum setiap dict dengan daftar () seperti: dict (daftar (x.items ()) + daftar + daftar (y.items ()))
justSaid
49

Sementara pertanyaan sudah dijawab beberapa kali, solusi sederhana untuk masalah ini belum terdaftar.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

Ini secepat z0 dan z2 jahat yang disebutkan di atas, tetapi mudah dimengerti dan diubah.

phobie
sumber
3
tapi ini tiga pernyataan daripada satu ekspresi
fortran
14
Iya! Solusi satu-ekspresi yang disebutkan itu lambat atau jahat. Kode yang baik dapat dibaca dan dipelihara. Jadi masalahnya adalah pertanyaannya bukan jawabannya. Kita harus meminta solusi terbaik dari masalah, bukan solusi satu garis.
phobie
7
Kalah z4 = {}dan ubah baris berikutnya ke z4 = x.copy()- lebih baik dari kode yang baik tidak melakukan hal-hal yang tidak perlu (yang membuatnya lebih mudah dibaca dan dipelihara).
martineau
3
Saran Anda akan mengubahnya menjadi jawaban Matthews. Sementara jawabannya baik-baik saja, saya pikir jawaban saya lebih mudah dibaca dan lebih bisa dipelihara. Jalur tambahan hanya akan menjadi buruk jika akan menghabiskan waktu eksekusi.
phobie
47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Di antara jawaban yang teduh dan meragukan, contoh cemerlang ini adalah satu-satunya cara yang baik untuk menggabungkan dikte dengan Python, disahkan oleh diktator seumur hidup Guido van Rossum sendiri! Orang lain menyarankan setengah dari ini, tetapi tidak menjalankannya.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

memberi:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}
Sam Watkins
sumber
39

Jika Anda berpikir lambda itu jahat maka jangan baca lebih lanjut. Seperti yang diminta, Anda dapat menulis solusi cepat dan efisien memori dengan satu ekspresi:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

Seperti yang disarankan di atas, menggunakan dua baris atau menulis fungsi mungkin merupakan cara yang lebih baik.

EMS
sumber
33

Menjadi pythonic. Gunakan pemahaman :

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}
Robino
sumber
1
Sebagai fungsi:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight
1
Simpan pencarian dengan melakukan iterasi pasangan kunci / nilai secara langsung:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger
30

Dalam python3, itemsmetode ini tidak lagi mengembalikan daftar , melainkan tampilan , yang bertindak seperti set. Dalam hal ini Anda harus mengambil serikat yang ditetapkan karena bergabung dengan +tidak akan bekerja:

dict(x.items() | y.items())

Untuk perilaku seperti python3 di versi 2.7, viewitemsmetode ini harus berfungsi sebagai pengganti items:

dict(x.viewitems() | y.viewitems())

Saya lebih suka notasi ini karena tampaknya lebih alami untuk menganggapnya sebagai operasi persatuan daripada gabungan (seperti yang ditunjukkan judulnya).

Edit:

Beberapa poin lagi untuk python 3. Pertama, perhatikan bahwa dict(x, **y)trik ini tidak akan bekerja di python 3 kecuali kunci-kunci di dalam yadalah string.

Juga, jawaban Chainmap Raymond Hettinger cukup elegan, karena dapat menggunakan jumlah dicts sebagai arbitrer, tetapi dari dokumen itu sepertinya berurutan melihat daftar semua dicts untuk setiap pencarian:

Pencarian mencari pemetaan yang mendasari berturut-turut sampai kunci ditemukan.

Ini dapat memperlambat Anda jika Anda memiliki banyak pencarian di aplikasi Anda:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

Jadi tentang urutan besarnya lebih lambat untuk pencarian. Saya penggemar Chainmap, tetapi terlihat kurang praktis di mana mungkin ada banyak pencarian.

jenggot
sumber
22

Penyalahgunaan mengarah ke solusi satu ekspresi untuk jawaban Matius :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

Anda bilang Anda menginginkan satu ekspresi, jadi saya menyalahgunakan lambdauntuk mengikat nama, dan tuple untuk mengesampingkan batas satu ekspresi lambda. Merasa ngeri.

Anda juga bisa melakukan ini tentu saja jika Anda tidak mau menyalinnya:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}
Claudiu
sumber
22

Solusi sederhana menggunakan itertools yang menjaga ketertiban (dicts terakhir diutamakan)

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

Dan penggunaannya:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}
Ruben
sumber
16

Meskipun jawabannya baik untuk kamus dangkal ini , tidak ada metode yang didefinisikan di sini yang benar-benar menggabungkan kamus dalam.

Contohnya mengikuti:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

Orang akan mengharapkan hasil dari sesuatu seperti ini:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

Sebaliknya, kami mendapatkan ini:

{'two': True, 'one': {'extra': False}}

Entri 'satu' harus memiliki 'depth_2' dan 'ekstra' sebagai item di dalam kamusnya jika benar-benar merupakan penggabungan.

Menggunakan rantai juga tidak berfungsi:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

Hasil dalam:

{'two': True, 'one': {'extra': False}}

Penggabungan mendalam yang diberikan rcwesick juga menciptakan hasil yang sama.

Ya, itu akan berhasil untuk menggabungkan kamus sampel, tetapi tidak satupun dari mereka adalah mekanisme umum untuk bergabung. Saya akan memperbarui ini nanti setelah saya menulis metode yang melakukan penggabungan yang benar.

Thanh Lim
sumber
11

(Hanya untuk Python2.7 *; ada solusi yang lebih sederhana untuk Python3 *.)

Jika Anda tidak keberatan mengimpor modul perpustakaan standar, Anda dapat melakukannya

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

( or aBit dalam lambdadiperlukan karena dict.updateselalu mengembalikan Nonekesuksesan.)

kjo
sumber
11

Jika Anda tidak keberatan bermutasi x,

x.update(y) or x

Sederhana, mudah dibaca, pemain. Anda tahu update() selalu kembali None, yang merupakan nilai salah. Jadi ungkapan di atas akan selalu dievaluasi x, setelah memperbaruinya.

Metode bermutasi di perpustakaan standar (seperti .update()) kembali Nonedengan konvensi, jadi pola ini juga akan bekerja pada mereka. Jika Anda menggunakan metode yang tidak mengikuti konvensi ini, maka ormungkin tidak berfungsi. Tapi, Anda bisa menggunakan tampilan tuple dan mengindeks untuk membuatnya menjadi ekspresi tunggal. Ini berfungsi terlepas dari apa elemen pertama mengevaluasi.

(x.update(y), x)[-1]

Jika Anda belum memiliki xvariabel, Anda dapat menggunakan lambdauntuk membuat lokal tanpa menggunakan pernyataan tugas. Jumlah ini menggunakan lambdasebagai ekspresi let , yang merupakan teknik umum dalam bahasa fungsional, tapi mungkin unpythonic.

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

Meskipun tidak jauh berbeda dengan penggunaan operator walrus baru berikut (hanya Python 3.8+):

(x := {'a': 1, 'b': 2}).update(y) or x

Jika Anda menginginkan salinan, gaya PEP 448 paling mudah {**x, **y}. Tetapi jika itu tidak tersedia di versi Python (lama) Anda, pola let berfungsi di sini juga.

(lambda z: z.update(y) or z)(x.copy())

(Itu, tentu saja, setara dengan (z := x.copy()).update(y) or z, tetapi jika versi Python Anda cukup baru untuk itu, maka gaya PEP 448 akan tersedia.)

Gilch
sumber
10

Menggambar pada ide di sini dan di tempat lain saya telah memahami suatu fungsi:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

Penggunaan (diuji dengan python 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

Anda bisa menggunakan lambda sebagai gantinya.

Bijou Trouvaille
sumber
10

Masalah yang saya miliki dengan solusi yang terdaftar hingga saat ini adalah bahwa, dalam kamus yang digabungkan, nilai untuk kunci "b" adalah 10 tetapi, menurut cara berpikir saya, itu harus 12. Dalam hal itu, saya menyajikan yang berikut:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

Hasil:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}
upandacross
sumber
1
Anda mungkin tertarik pada cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli
10

Sangat konyol sehingga .updatetidak menghasilkan apa-apa.
Saya hanya menggunakan fungsi pembantu sederhana untuk memecahkan masalah:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

Contoh:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy
Mendapatkan gratis
sumber
10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Ini harus menyelesaikan masalah Anda.

reetesh11
sumber
9

Ini dapat dilakukan dengan pemahaman dict tunggal:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

Dalam pandangan saya jawaban terbaik untuk bagian 'ekspresi tunggal' karena tidak ada fungsi tambahan yang diperlukan, dan itu pendek.

RemcoGerlich
sumber
Saya menduga kinerja tidak akan terlalu bagus; membuat set dari setiap dict kemudian hanya beralih melalui tombol berarti pencarian lain untuk nilai setiap kali (meskipun relatif cepat, masih meningkatkan urutan fungsi untuk penskalaan)
Breezer
2
itu semua tergantung pada versi python yang kita gunakan. Dalam 3.5 dan di atas {** x, ** y} memberikan kamus bersambung
Rashid Mv
9

Akan ada opsi baru ketika Python 3.8 rilis ( dijadwalkan untuk 20 Oktober 2019) ), terima kasih kepada PEP 572: Ekspresi Penugasan . Operator ekspresi tugas baru :=memungkinkan Anda untuk menetapkan hasil copydan masih menggunakannya untuk memanggil update, meninggalkan kode gabungan satu ekspresi, daripada dua pernyataan, mengubah:

newdict = dict1.copy()
newdict.update(dict2)

untuk:

(newdict := dict1.copy()).update(dict2)

sementara berperilaku identik dalam segala hal. Jika Anda juga harus mengembalikan hasilnya dict(Anda meminta ekspresi yang mengembalikan dict; yang di atas membuat dan menetapkan newdict, tetapi tidak mengembalikannya, jadi Anda tidak bisa menggunakannya untuk meneruskan argumen ke fungsi apa adanya, ala myfunc((newdict := dict1.copy()).update(dict2))) , lalu tambahkan saja or newdictsampai akhir (sejakupdate return None, yang salah, ia akan mengevaluasi dan kembali newdictsebagai hasil dari ekspresi):

(newdict := dict1.copy()).update(dict2) or newdict

Peringatan penting: Secara umum, saya akan mengecilkan pendekatan ini demi:

newdict = {**dict1, **dict2}

Pendekatan membongkar lebih jelas (bagi siapa pun yang tahu tentang pembongkaran umum di tempat pertama, yang harus Anda lakukan ), tidak memerlukan nama untuk hasilnya sama sekali (jadi jauh lebih ringkas ketika membangun sementara yang segera diteruskan ke sebuah fungsi atau termasuk dalam list/ tupleliteral atau sejenisnya), dan hampir pasti lebih cepat juga, karena (pada CPython) kira-kira setara dengan:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

tetapi dilakukan pada lapisan C, menggunakan dictAPI beton , sehingga tidak ada metode dinamis lookup / binding atau overhead panggilan fungsi pengiriman yang terlibat (di mana (newdict := dict1.copy()).update(dict2)tak terhindarkan identik dengan perilaku dua-liner asli, melakukan pekerjaan dalam langkah-langkah terpisah, dengan pencarian dinamis / mengikat / doa metode.

Ini juga lebih dapat diperluas, karena menggabungkan tiga dict s sudah jelas:

 newdict = {**dict1, **dict2, **dict3}

di mana menggunakan ekspresi penugasan tidak akan skala seperti itu; yang terdekat dengan Anda adalah:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

atau tanpa Nonet sementara sementara , tetapi dengan pengujian kebenaran dari setiap Nonehasil:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

salah satu dari yang jelas jauh lebih buruk, dan termasuk inefisiensi lebih lanjut (baik terbuang sementara tupledari Nones untuk pemisahan koma, atau pengujian truthiness sia-sia setiap update's Noneimbalan orpemisahan).

Satu-satunya keuntungan nyata untuk pendekatan ekspresi penugasan terjadi jika:

  1. Anda memiliki kode generik yang perlu ditangani baik sets dan dicts (keduanya mendukung copydan update, jadi kodenya berfungsi kurang lebih seperti yang Anda harapkan)
  2. Anda berharap untuk menerima objek seperti dict yang sewenang-wenang , bukan hanya dictdirinya sendiri, dan harus menjaga jenis dan semantik sisi kiri (daripada berakhir dengan dataran dict). Meskipun myspecialdict({**speciala, **specialb})mungkin bekerja, itu akan melibatkan ekstra temporer dict, dan jika myspecialdictmemiliki fitur yang dicttidak dapat dipertahankan (mis. Reguler dictsekarang mempertahankan pesanan berdasarkan tampilan pertama kunci, dan nilai berdasarkan tampilan kunci terakhir; Anda mungkin ingin salah satu yang mempertahankan ketertiban berdasarkan yang terakhirpenampilan kunci sehingga memperbarui nilai juga memindahkannya ke akhir), maka semantik akan salah. Karena versi ekspresi penugasan menggunakan metode yang dinamai (yang mungkin kelebihan beban untuk berperilaku sesuai), itu tidak pernah membuatdictsama sekali (kecuali dict1sudah a dict), melestarikan tipe asli (dan semantik tipe asli), sambil menghindari sementara.
ShadowRanger
sumber
8
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}
John La Rooy
sumber
Metode ini menimpa xdengan salinannya. Jika xargumen fungsi ini tidak akan berfungsi (lihat contoh )
bartolo-otrit