ฉันจะรวมพจนานุกรมสองเล่มในนิพจน์เดียวใน Python ได้อย่างไร


4783

ฉันมีพจนานุกรม Python สองตัวและฉันต้องการเขียนนิพจน์เดียวที่ส่งคืนพจนานุกรมสองเล่มนี้ที่รวมกัน update()วิธีการจะเป็นสิ่งที่ฉันต้องการถ้ามันกลับผลของมันแทนการปรับเปลี่ยนพจนานุกรมในสถานที่

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

ฉันจะรับพจนานุกรมที่ผสานขั้นสุดท้ายzได้xอย่างไร?

(เพื่อความชัดเจนเป็นพิเศษการจัดการความขัดแย้งครั้งสุดท้ายdict.update()เป็นสิ่งที่ฉันกำลังมองหาเช่นกัน)


หากคุณกำลังใช้ Python 3.9 alpha เพียงใช้z = x | y
The Daleks

คำตอบ:


5692

ฉันจะรวมพจนานุกรม Python สองพจนานุกรมเข้าด้วยกันในนิพจน์เดียวได้อย่างไร

สำหรับพจนานุกรมxและy, zกลายเป็นตื้นรวมพจนานุกรมที่มีค่าจากการเปลี่ยนจากyx

  • ใน Python 3.5 หรือสูงกว่า:

    z = {**x, **y}
  • ใน Python 2 (หรือ 3.4 หรือต่ำกว่า) เขียนฟังก์ชัน:

    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

    และตอนนี้:

    z = merge_two_dicts(x, y)
  • ในหลาม 3.9.0a4 หรือมากกว่าวันที่ (รุ่นสุดท้ายประมาณตุลาคม 2020): PEP-584 , กล่าวถึงที่นี่ได้รับการดำเนินการเพื่อให้ง่ายต่อไปนี้:

    z = x | y          # NOTE: 3.9+ ONLY

คำอธิบาย

สมมติว่าคุณมีสอง dicts และคุณต้องการรวมมันเข้าไปใน dict ใหม่โดยไม่ต้องเปลี่ยน dicts ดั้งเดิม:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

ผลลัพธ์ที่ต้องการคือการรับพจนานุกรมใหม่ ( z) ด้วยค่าที่ผสานและค่าของ dict ที่สองเขียนทับสิ่งเหล่านั้นตั้งแต่แรก

>>> z
{'a': 1, 'b': 3, 'c': 4}

ไวยากรณ์ใหม่สำหรับสิ่งนี้ซึ่งเสนอในPEP 448และมีให้ตั้งแต่ Python 3.5คือ

z = {**x, **y}

และมันก็เป็นเพียงนิพจน์เดียว

โปรดทราบว่าเราสามารถผสานด้วยสัญกรณ์ตามตัวอักษรได้เช่นกัน:

z = {**x, 'foo': 1, 'bar': 2, **y}

และตอนนี้:

>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}

ตอนนี้มันแสดงให้เห็นว่ามีการใช้งานในกำหนดการวางตลาดสำหรับ 3.5, PEP 478และตอนนี้ได้นำมาสู่สิ่งใหม่ในเอกสารPython 3.5

อย่างไรก็ตามเนื่องจากหลายองค์กรยังคงใช้งาน Python 2 อยู่คุณอาจต้องการทำสิ่งนี้ด้วยวิธีที่เข้ากันได้แบบย้อนหลัง วิธี Pythonic แบบคลาสสิกที่มีอยู่ใน Python 2 และ Python 3.0-3.4 คือการทำเช่นนี้เป็นกระบวนการสองขั้นตอน:

z = x.copy()
z.update(y) # which returns None since it mutates z

ในทั้งสองวิธีyจะมาที่สองและค่าของมันจะแทนที่xค่าของดังนั้นจึง'b'จะชี้ไปที่3ผลลัพธ์สุดท้ายของเรา

ยังไม่ได้ใช้ Python 3.5 แต่ต้องการนิพจน์เดียว

หากคุณยังไม่ได้ใช้ Python 3.5 หรือต้องการเขียนโค้ดที่เข้ากันได้แบบย้อนหลังและคุณต้องการสิ่งนี้ในการแสดงออกครั้งเดียวผู้ที่มีประสิทธิภาพมากที่สุดในขณะที่วิธีการที่ถูกต้องคือการใส่มันลงในฟังก์ชั่น:

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

แล้วคุณมีนิพจน์เดียว:

z = merge_two_dicts(x, y)

คุณสามารถสร้างฟังก์ชันเพื่อรวมจำนวน dicts ที่ไม่ได้กำหนดจากศูนย์ถึงจำนวนมาก:

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

ฟังก์ชั่นนี้จะทำงานใน Python 2 และ 3 สำหรับ dicts ทั้งหมด เช่นให้ dicts aกับg:

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

และคู่ของค่าคีย์gจะมีความสำคัญมากกว่า dicts aถึงfและอื่น ๆ

คำวิจารณ์ของคำตอบอื่น ๆ

อย่าใช้สิ่งที่คุณเห็นในคำตอบที่ยอมรับก่อนหน้านี้:

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

ใน Python 2 คุณสร้างสองรายการในหน่วยความจำสำหรับแต่ละ dict สร้างรายการที่สามในหน่วยความจำที่มีความยาวเท่ากับความยาวของสองรายการแรกที่รวมกันแล้วละทิ้งรายการทั้งสามเพื่อสร้าง dict ใน Python 3 สิ่งนี้จะล้มเหลวเพราะคุณกำลังเพิ่มdict_itemsวัตถุสองชิ้นเข้าด้วยกันไม่ใช่สองรายการ -

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

z = dict(list(x.items()) + list(y.items()))และคุณจะต้องชัดเจนสร้างพวกเขาเป็นรายการเช่น นี่เป็นการสิ้นเปลืองทรัพยากรและอำนาจในการคำนวณ

ในทำนองเดียวกันการรวมกันของitems()ใน Python 3 ( viewitems()ใน Python 2.7) จะล้มเหลวเมื่อค่าเป็นวัตถุที่ไม่สามารถล้างได้ (เช่นรายการเช่น) แม้ว่าค่าของคุณจะแฮชได้เนื่องจากชุดนั้นไม่ได้เรียงลำดับความหมาย แต่พฤติกรรมนั้นไม่ได้ถูกกำหนดโดยคำนึงถึงลำดับความสำคัญ ดังนั้นอย่าทำสิ่งนี้:

>>> c = dict(a.items() | b.items())

ตัวอย่างนี้แสดงให้เห็นถึงสิ่งที่เกิดขึ้นเมื่อค่า unhashable:

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

นี่คือตัวอย่างที่ y ควรมีลำดับความสำคัญ แต่แทนที่จะเก็บค่าจาก x เนื่องจากลำดับของชุดโดยพลการ:

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

แฮ็คอื่นที่คุณไม่ควรใช้:

z = dict(x, **y)

สิ่งนี้ใช้ตัวdictสร้างและรวดเร็วมากและมีประสิทธิภาพของหน่วยความจำ (ยิ่งกว่าเล็กน้อยดังนั้นมากกว่ากระบวนการสองขั้นตอนของเรา) แต่หากคุณไม่ทราบว่าเกิดอะไรขึ้นที่นี่ คอนสตรัคเตอร์) มันยากที่จะอ่านไม่ใช่การใช้ที่ตั้งใจและไม่ Pythonic

นี่คือตัวอย่างของการใช้งานที่ถูกremediated ใน Django

Dicts มีจุดประสงค์เพื่อใช้คีย์ hashable (เช่น frozensets หรือ tuples) แต่วิธีนี้ล้มเหลวใน Python 3 เมื่อคีย์ไม่ใช่สตริง

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

จากรายชื่อผู้รับจดหมาย Guido van Rossum ผู้สร้างภาษาเขียนว่า:

ฉันสบายดีกับการประกาศ dict ({}, ** {1: 3}) ที่ผิดกฎหมายเพราะหลังจากนั้นมันเป็นการละเมิดกลไก **

และ

เห็นได้ชัดว่า dict (x, ** y) กำลังดำเนินไปในลักษณะ "cool hack" สำหรับ "call x.update (y) และ return x" โดยส่วนตัวแล้วฉันพบว่ามันน่ารังเกียจยิ่งกว่าเท่ห์

มันเป็นความเข้าใจของฉัน (รวมถึงความเข้าใจของผู้สร้างภาษา ) ที่การใช้งานที่ตั้งใจไว้dict(**y)สำหรับการสร้าง dicts เพื่อวัตถุประสงค์ในการอ่านเช่น:

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

แทน

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

ตอบสนองต่อความคิดเห็น

แม้จะมีสิ่งที่กุยโด้กล่าวว่าdict(x, **y)เป็นไปตามข้อกำหนด dict ซึ่ง btw ใช้งานได้กับทั้ง Python 2 และ 3 ข้อเท็จจริงที่ว่าการทำงานเฉพาะกับคีย์สตริงนั้นเป็นผลโดยตรงจากการทำงานของพารามิเตอร์คำหลักและไม่ใช่การใช้คำสั่งสั้น ๆ หรือใช้ตัวดำเนินการ ** ในที่นี้เป็นการละเมิดกลไกในความเป็นจริง ** ได้รับการออกแบบมาอย่างแม่นยำเพื่อส่งผ่านการกำหนดเป็นคำหลัก

อีกครั้งมันไม่ทำงานสำหรับ 3 เมื่อคีย์ไม่ใช่สตริง สัญญาการโทรโดยนัยคือเนมสเปซใช้ dicts สามัญในขณะที่ผู้ใช้จะต้องส่งผ่านอาร์กิวเมนต์คำหลักที่เป็นสตริงเท่านั้น callables อื่นทั้งหมดบังคับใช้ dictทำลายความสอดคล้องนี้ใน 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}

ความไม่สอดคล้องกันนี้ไม่ดีเนื่องจากการนำ Python ไปใช้งานแบบอื่น (Pypy, Jython, IronPython) ดังนั้นจึงได้รับการแก้ไขใน Python 3 เนื่องจากการใช้งานนี้อาจเป็นการเปลี่ยนแปลงที่ไม่สมบูรณ์

ฉันส่งให้คุณว่ามันเป็นการไร้ความสามารถที่เป็นอันตรายในการเขียนโค้ดโดยเจตนาซึ่งใช้งานได้ในภาษาหนึ่งเวอร์ชันเท่านั้นหรือใช้งานได้เพียงบางข้อเท่านั้น

ความคิดเห็นเพิ่มเติม:

dict(x.items() + y.items()) ยังคงเป็นโซลูชันที่อ่านได้มากที่สุดสำหรับ Python 2 จำนวนการอ่านได้

การตอบสนองของฉัน: merge_two_dicts(x, y)จริง ๆ แล้วดูเหมือนชัดเจนสำหรับฉันถ้าเรากังวลเกี่ยวกับการอ่าน และมันไม่รองรับการใช้งานในอนาคตเนื่องจาก Python 2 เลิกใช้งานมากขึ้น

{**x, **y}ดูเหมือนจะไม่จัดการพจนานุกรมที่ซ้อนกัน เนื้อหาของปุ่มที่ซ้อนกันนั้นเขียนทับง่ายไม่รวม [... ] ฉันสิ้นสุดการถูกเผาโดยคำตอบเหล่านี้ที่ไม่รวมการเรียกซ้ำและฉันรู้สึกประหลาดใจที่ไม่มีใครพูดถึงมัน ในการตีความของฉันของคำว่า "การรวม" คำตอบเหล่านี้อธิบาย "ปรับปรุงหนึ่ง dict กับอีก" และไม่รวม

ใช่. ผมต้องดูให้คุณกลับไปที่คำถามซึ่งจะขอตื้นผสานสองพจนานุกรมมีค่าเป็นครั้งแรกที่ถูกเขียนทับโดยที่สอง - ในการแสดงออกเดียว

สมมติว่าพจนานุกรมสองพจนานุกรมหนึ่งพจนานุกรมหนึ่งอาจรวมเข้าด้วยกันในฟังก์ชันเดียว แต่คุณควรระวังไม่แก้ไข dicts จากแหล่งใดแหล่งหนึ่งและวิธีที่แน่นอนที่สุดเพื่อหลีกเลี่ยงการทำสำเนาเมื่อทำการกำหนดค่า เนื่องจากคีย์ต้อง hashable และมักจะไม่เปลี่ยนรูปดังนั้นจึงไม่มีประโยชน์ที่จะคัดลอก:

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

การใช้งาน:

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

ขึ้นมาพร้อมกับภาระผูกพันสำหรับประเภทค่าอื่น ๆ ที่อยู่ไกลเกินขอบเขตของคำถามนี้ดังนั้นผมจะชี้ให้คุณในคำตอบของฉันไปที่คำถามที่ยอมรับใน "พจนานุกรมพจนานุกรมรวม"

ประสิทธิภาพน้อยลง แต่แก้ไขโฆษณา

วิธีการเหล่านี้มีประสิทธิภาพน้อยกว่า แต่จะให้พฤติกรรมที่ถูกต้อง พวกเขาจะมากน้อย performant กว่าcopyและupdateหรือเอาออกใหม่เพราะพวกเขาย้ำผ่านแต่ละคู่สำคัญที่มีมูลค่าในระดับที่สูงขึ้นของนามธรรม แต่พวกเขาทำเคารพคำสั่งของลำดับความสำคัญ (dicts หลังมีความสำคัญ)

นอกจากนี้คุณยังสามารถเชื่อมโยง dicts ด้วยตนเองภายใน dict comprehension:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

หรือในไพ ธ อน 2.6 (และอาจเร็วเท่า 2.4 เมื่อมีการแนะนำนิพจน์เครื่องกำเนิดไฟฟ้า):

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain จะโยงตัววนซ้ำไปยังคู่คีย์ - ค่าตามลำดับที่ถูกต้อง:

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

การวิเคราะห์ประสิทธิภาพ

ฉันแค่จะทำการวิเคราะห์ประสิทธิภาพของประเพณีที่รู้จักกันว่าทำงานอย่างถูกต้อง

import timeit

สิ่งต่อไปนี้ทำบน Ubuntu 14.04

ใน Python 2.7 (ระบบ 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

ใน 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

แหล่งข้อมูลเกี่ยวกับพจนานุกรม


9
@MohammadAzim "strings only" ใช้กับการขยายอาร์กิวเมนต์คำหลักใน callables เท่านั้นไม่ใช่ไวยากรณ์ทั่วไปที่ไม่ได้เปิดออก เพื่อแสดงให้เห็นว่าสิ่งนี้ใช้ได้ผล: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall

37
คำตอบสั้น ๆ เช่นz = {**x, **y}กระตุ้นฉันจริงๆ
pcko1

1
สิ่งนี้อาจเปลี่ยนแปลงได้เมื่อยอมรับ PEP-0584 ผู้ประกอบการสหภาพใหม่จะดำเนินการด้วยไวยากรณ์ต่อไปนี้:x | y
Callam Delaney

2
เมื่อคำตอบต้องการสรุปที่ด้านบนมันยาวเกินไป
Gringo Suave

2
สวัสดีด้านบนเป็นบทสรุปใช่ แล้วแต่คุณ. สิ่งทั้งหมดจะเป็นการโพสต์บล็อกที่ยอดเยี่ยม หมายเหตุ Py 3.4 และต่ำกว่าคือ EOL, 3.5 กำลังเข้าใกล้ EOL ในปี 2020-09
Gringo Suave

1617

ในกรณีของคุณสิ่งที่คุณสามารถทำได้คือ:

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

สิ่งนี้จะเป็นไปตามที่คุณต้องการวางคำสั่งสุดท้ายzและทำให้ค่าสำหรับคีย์bถูกแทนที่อย่างถูกต้องโดยค่าที่สอง ( y) ของ Dict:

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

ถ้าคุณใช้ Python 3 มันซับซ้อนกว่านี้เล็กน้อย วิธีสร้างz:

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

หากคุณใช้ Python เวอร์ชัน 3.9.0a4 หรือสูงกว่าคุณสามารถใช้งานได้โดยตรง:

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

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

2
อย่าใช้สิ่งนี้เพราะมันไม่มีประสิทธิภาพมาก (ดูผลลัพธ์ timeit ด้านล่าง) อาจมีความจำเป็นใน Py2 วันถ้าฟังก์ชั่น wrapper ไม่ใช่ตัวเลือก แต่วันเหล่านั้นผ่านไปแล้ว
Gringo Suave

633

ทางเลือก:

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

83
เพื่อชี้แจงว่าเหตุใดสิ่งนี้จึงไม่เป็นไปตามบทวิจารณ์ที่มาจากคำถาม: มันไม่ใช่นิพจน์เดียวและไม่ส่งคืน z
Alex

2
@neononet ทุก ๆ oneliner มักจะเป็นแค่การย้ายโค้ดที่ต้องเกิดขึ้นเป็นส่วนประกอบที่แตกต่างกันและแก้ไขมันที่นั่น นี่เป็นหนึ่งในกรณีที่แน่นอน แต่ภาษาอื่นมีโครงสร้างที่ดีกว่า python สำหรับสิ่งนี้ และการมีตัวแปรโปร่งใสแบบอ้างอิงที่ส่งคืนองค์ประกอบมันเป็นสิ่งที่ดีที่จะมี
อเล็กซ์

12
ใช้วิธีนี้: หากคุณต้องการใส่ความคิดเห็นสองบรรทัดเพื่ออธิบายโค้ดหนึ่งบรรทัดให้กับคนที่คุณส่งรหัสไปที่ ... คุณเคยทำมันในบรรทัดเดียวหรือไม่? :) ฉันเห็นด้วยอย่างยิ่งว่า Python ไม่ดีสำหรับเรื่องนี้: ควรมีวิธีที่ง่ายกว่านี้มาก ในขณะที่คำตอบนี้เป็นแบบ pythonic มากกว่านั้นจริง ๆ ทั้งหมดที่ชัดเจนหรือชัดเจน? Updateไม่ใช่ฟังก์ชั่น "แกนกลาง" ที่ผู้คนมักจะใช้งานบ่อย
eric

ถ้าผู้คนยืนยันที่จะทำให้มันเป็นแบบออนไลน์คุณสามารถทำได้เสมอ(lambda z: z.update(y) or z)(x.copy()): P
towr

340

ตัวเลือกอื่นที่กระชับยิ่งขึ้น:

z = dict(x, **y)

หมายเหตุ : สิ่งนี้ได้กลายเป็นคำตอบที่ได้รับความนิยม แต่สิ่งสำคัญคือต้องชี้ให้เห็นว่าหากyมีคีย์ที่ไม่ใช่สตริงความจริงที่ว่าสิ่งนี้ใช้ได้ทั้งหมดคือการใช้รายละเอียดการใช้งานของ CPython ในทางที่ผิด หรือใน PyPy, IronPython หรือ Jython นอกจากนี้กุยคือไม่ได้เป็นแฟน ดังนั้นฉันจึงไม่สามารถแนะนำเทคนิคนี้สำหรับโค้ดแบบพกพาที่สามารถใช้งานร่วมกันได้หรือ cross-implementation


ทำงานได้ดีใน Python 3 และ PyPy และ PyPy 3ไม่สามารถพูดกับ Jython หรือ Iron ได้ เมื่อกำหนดรูปแบบนี้จะได้รับการบันทึกไว้อย่างชัดเจน (ดูฟอร์มตัวสร้างที่สามในเอกสารนี้) ฉันขอยืนยันว่าไม่ใช่ "รายละเอียดการใช้งาน" แต่เป็นการใช้คุณลักษณะโดยเจตนา
amcgregor

5
@amcgregor คุณพลาดคำสำคัญ "ถ้า y มีคีย์ที่ไม่ใช่สตริง" นั่นคือสิ่งที่ไม่ทำงานใน Python3 ความจริงที่ว่ามันใช้งานได้ใน CPython 2 เป็นรายละเอียดการใช้งานที่ไม่สามารถเชื่อถือได้ IFF คีย์ทั้งหมดของคุณรับประกันว่าจะเป็นสตริงนี่เป็นตัวเลือกที่รองรับอย่างสมบูรณ์
Carl Meyer

214

นี่อาจไม่ใช่คำตอบที่ได้รับความนิยม แต่คุณไม่ต้องการทำเช่นนี้ หากคุณต้องการสำเนาที่ผสานให้ใช้ copy (หรือdeepcopyขึ้นอยู่กับสิ่งที่คุณต้องการ) แล้วอัปเดต โค้ดสองบรรทัดสามารถอ่านได้มากขึ้น - Pythonic มากกว่า - มากกว่าการสร้างบรรทัดเดียวด้วย. item () + .items () ชัดเจนดีกว่าโดยปริยาย

นอกจากนี้เมื่อคุณใช้. ไอเท็ม () (ก่อน Python 3.0) คุณจะสร้างรายการใหม่ที่มีรายการจาก dict หากพจนานุกรมของคุณมีขนาดใหญ่แสดงว่าเป็นค่าโสหุ้ยค่อนข้างมาก (รายการใหญ่สองรายการที่จะถูกโยนทิ้งทันทีที่มีการสร้างทบผสาน) update () สามารถทำงานได้อย่างมีประสิทธิภาพมากขึ้นเพราะมันสามารถทำงานผ่าน dict รายการที่สองโดยรายการ

ในแง่ของเวลา :

>>> 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 การชะลอตัวเล็กน้อยระหว่างสองรายการแรกนั้นคุ้มค่าสำหรับการอ่าน นอกจากนี้ยังมีการเพิ่มอาร์กิวเมนต์คำหลักสำหรับการสร้างพจนานุกรมใน Python 2.3 เท่านั้นส่วน copy () และ update () จะทำงานในเวอร์ชันที่เก่ากว่า


150

ในคำตอบติดตามคุณถามเกี่ยวกับประสิทธิภาพที่สัมพันธ์กันของสองตัวเลือกนี้:

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

บนเครื่องของฉันอย่างน้อย (x86_64 ธรรมดาที่ใช้ Python 2.5.2) z2เป็นทางเลือกไม่เพียง แต่สั้นและง่ายกว่า แต่ยังเร็วกว่ามาก คุณสามารถยืนยันสิ่งนี้ได้ด้วยตัวเองโดยใช้timeitโมดูลที่มาพร้อมกับ Python

ตัวอย่างที่ 1: พจนานุกรมที่เหมือนกันจับคู่จำนวนเต็ม 20 ตัวต่อเนื่องกัน:

% 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ชนะโดยปัจจัย 3.5 หรือมากกว่านั้น พจนานุกรมที่แตกต่างกันดูเหมือนจะให้ผลลัพธ์ที่แตกต่างกันมาก แต่z2ก็ดูเหมือนจะออกมาข้างหน้าเสมอ (หากคุณได้รับผลลัพธ์ที่ไม่สอดคล้องกันสำหรับการทดสอบเดียวกันให้ลองผ่าน-rด้วยตัวเลขที่มากกว่าค่าเริ่มต้น 3)

ตัวอย่างที่ 2: พจนานุกรมที่ไม่มีการซ้อนทับกันการจับคู่สตริง 252 สั้นกับจำนวนเต็มและในทางกลับกัน:

% 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 ชนะประมาณ 10 เท่านั่นเป็นชัยชนะที่ยิ่งใหญ่ในหนังสือของฉัน!

หลังจากเปรียบเทียบสองสิ่งนี้ฉันสงสัยว่าz1ประสิทธิภาพที่ไม่ดีอาจเป็นผลมาจากค่าใช้จ่ายในการสร้างรายการสองรายการซึ่งทำให้ฉันสงสัยว่ารูปแบบนี้อาจทำงานได้ดีขึ้นหรือไม่:

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

การทดสอบอย่างรวดเร็วบางประการเช่น

% 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

นำฉันไปสู่ข้อสรุปที่z3ค่อนข้างเร็วกว่าz1แต่ไม่เร็วเท่าz2แต่ไม่ได้เกือบเป็นอย่างรวดเร็วแน่นอนว่าไม่คุ้มค่ากับการพิมพ์เพิ่มเติมทั้งหมด

การสนทนานี้ยังขาดสิ่งที่สำคัญซึ่งเป็นการเปรียบเทียบประสิทธิภาพของทางเลือกเหล่านี้ด้วยวิธี "ชัดเจน" ของการรวมสองรายการ: การใช้updateวิธีการ ในการพยายามเก็บสิ่งต่าง ๆ อย่างเท่าเทียมกับนิพจน์ไม่มีสิ่งใดที่แก้ไข x หรือ y ฉันจะทำสำเนาของ x แทนการแก้ไขในสถานที่ดังนี้:

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

ผลลัพธ์ทั่วไป:

% 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

ในคำอื่น ๆz0และz2ดูเหมือนว่าจะมีประสิทธิภาพเหมือนกัน คุณคิดว่านี่อาจเป็นเรื่องบังเอิญหรือไม่? ฉันไม่....

ในความเป็นจริงฉันจะไปไกลเกินกว่าที่จะอ้างว่าเป็นไปไม่ได้ที่รหัส Python บริสุทธิ์จะทำอะไรได้ดีไปกว่านี้ และถ้าคุณสามารถทำได้ดีขึ้นอย่างมากในโมดูลส่วนขยาย C ฉันคิดว่า Python folks อาจสนใจที่จะรวมรหัสของคุณ (หรือการเปลี่ยนแปลงในแนวทางของคุณ) เข้ากับแกน Python Python ใช้dictในหลายสถานที่ การเพิ่มประสิทธิภาพการดำเนินงานเป็นเรื่องใหญ่

คุณสามารถเขียนสิ่งนี้เป็น

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

อย่างที่โทนี่ทำ แต่ (ไม่น่าประหลาดใจ) ความแตกต่างในสัญกรณ์ปรากฎว่าไม่มีผลกระทบใด ๆ ที่วัดได้ต่อประสิทธิภาพการทำงาน ใช้สิ่งที่เหมาะสมกับคุณ แน่นอนว่าเขาถูกต้องอย่างยิ่งที่จะชี้ให้เห็นว่าเวอร์ชั่นสองข้อความนั้นง่ายกว่าที่จะเข้าใจ


5
สิ่งนี้ไม่ทำงานใน Python 3 items()ไม่สามารถจัดการได้และiteritemsไม่มีอยู่
Antti Haapala

127

ใน Python 3.0 และใหม่กว่าคุณสามารถใช้collections.ChainMapกลุ่ม dicts หลายอันหรือการแมปอื่นร่วมกันเพื่อสร้างมุมมองที่อัปเดตเดียว:

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

อัปเดตสำหรับ Python 3.5 และใหม่กว่า : คุณสามารถใช้การบรรจุพจนานุกรมขยายPEP 448และการเปิดออกได้ นี่คือง่ายและรวดเร็ว:

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

3
แต่สิ่งหนึ่งที่ควรระวังในขณะที่ใช้ ChainMap มีการตรวจจับว่าหากคุณมีคีย์ที่ซ้ำกันค่าจากการแม็พแรกจะถูกใช้และเมื่อคุณเรียกใช้delon a say ChainMap c จะลบการแม็พแรกของคีย์นั้น
สังหาร

7
@Pererit คุณคาดหวังอะไรอีก นั่นเป็นวิธีปกติในการทำงานของเนมสเปซเชน พิจารณาว่า $ PATH ทำงานอย่างไรในการทุบตี การลบไฟล์ที่เรียกใช้งานได้บนพา ธ นั้นไม่ได้เป็นการป้องกันไฟล์อื่นที่มีชื่อเดียวกัน
Raymond Hettinger

2
@Raymond Hettinger ฉันเห็นด้วยเพิ่งเพิ่มความระมัดระวัง คนส่วนใหญ่อาจไม่รู้เกี่ยวกับเรื่องนี้ : D
Slayer

@Pererit คุณสามารถนำไปใช้dictเพื่อหลีกเลี่ยงสิ่งนั้นเช่น:dict(ChainMap({}, y, x))
wjandrea

113

ฉันต้องการสิ่งที่คล้ายกัน แต่ด้วยความสามารถในการระบุวิธีการรวมค่าของคีย์ที่ซ้ำกันดังนั้นฉันจึงแฮ็คสิ่งนี้ (แต่ไม่ได้ทดสอบอย่างหนัก) เห็นได้ชัดว่านี่ไม่ใช่นิพจน์เดียว แต่เป็นการเรียกใช้ฟังก์ชันเดียว

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

88

ซ้ำ ๆ / ลึกอัปเดต dict

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

สาธิต:

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

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

print deepupdate(pluto_original, pluto_update)

ขาออก:

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

ขอบคุณ rednaw สำหรับการแก้ไข


1
นี่ไม่ได้ตอบคำถาม คำถามจะถามหาพจนานุกรมใหม่อย่างชัดเจน z จากพจนานุกรมต้นฉบับ x และ y โดยมีค่าจาก y แทนพจนานุกรม x - ไม่ใช่พจนานุกรมที่อัปเดต คำตอบนี้แก้ไข y ในสถานที่โดยการเพิ่มค่าจาก x ยิ่งไปกว่านั้นมันไม่ได้คัดลอกค่าเหล่านี้ดังนั้นจึงสามารถแก้ไขพจนานุกรมที่แก้ไขเพิ่มเติมได้ y และการปรับเปลี่ยนอาจสะท้อนในพจนานุกรม x @ Jérômeฉันหวังว่ารหัสนี้จะไม่ก่อให้เกิดข้อบกพร่องใด ๆ สำหรับแอปพลิเคชันของคุณ - อย่างน้อยให้พิจารณาใช้ deepcopy เพื่อคัดลอกค่า
Aaron Hall

1
@AaronHall ตกลงนี้ไม่ตอบคำถาม แต่มันก็ตอบสนองความต้องการของฉัน ฉันเข้าใจข้อ จำกัด เหล่านั้น แต่นั่นไม่ใช่ปัญหาในกรณีของฉัน คิดว่าอาจเป็นชื่อที่ทำให้เข้าใจผิดเพราะมันอาจทำให้เกิดการพิมพ์ลึกซึ่งมันไม่ได้ให้ แต่มันเน้นการทำรังลึก นี่คือการดำเนินงานอื่นจาก Martellibot นี้: stackoverflow.com/questions/3232943/...
Jérôme

72

รุ่นที่ดีที่สุดที่ฉันสามารถคิดได้ในขณะที่ไม่ใช้การคัดลอกคือ

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

มันเร็วกว่าdict(x.items() + y.items())แต่ไม่เร็วเท่าn = copy(a); n.update(b)อย่างน้อยใน CPython รุ่นนี้ยังใช้งานได้ใน Python 3 หากคุณเปลี่ยนiteritems()เป็นitems()ซึ่งจะทำโดยอัตโนมัติด้วยเครื่องมือ 2to3

โดยส่วนตัวแล้วฉันชอบเวอร์ชั่นนี้ดีที่สุดเพราะมันอธิบายได้ค่อนข้างดีสิ่งที่ฉันต้องการในซินแท็กซ์การทำงานเดี่ยว ปัญหาเล็กน้อยเพียงอย่างเดียวคือมันไม่ได้ชัดเจนว่าค่าจาก y จะมีความสำคัญเหนือกว่าค่าจาก x แต่ฉันไม่เชื่อว่ามันยากที่จะเข้าใจ


71

Python 3.5 (PEP 448) อนุญาตให้ใช้ตัวเลือกไวยากรณ์ของ nicer:

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

หรือแม้กระทั่ง

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

ใน Python 3.9 คุณยังใช้ | และ | = ด้วยตัวอย่างด้านล่างจาก PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

วิธีนี้เป็นวิธีที่ดีกว่าdict(x, **y)-solution? ในขณะที่คุณ (@CarlMeyer) กล่าวถึงภายในทราบคำตอบของคุณเอง (คนstackoverflow.com/a/39858/2798610 ) กุยพิจารณาแก้ปัญหาที่ผิดกฎหมาย
Blackeagle52

14
กุยโด้ไม่ชอบdict(x, **y)ด้วยเหตุผล (ดีมาก) ที่ต้องอาศัยyการมีคีย์ซึ่งเป็นชื่ออาร์กิวเมนต์ของคำหลักที่ถูกต้องเท่านั้น (เว้นแต่ว่าคุณกำลังใช้ CPython 2.7 ซึ่งเป็นตัวสร้างสูตร dict) การคัดค้าน / การ จำกัด นี้ไม่ได้ใช้กับ PEP 448 ซึ่งสรุป**ไวยากรณ์ที่ไม่ได้บรรจุไว้กับตัวอักษร dict ดังนั้นวิธีนี้จึงมีความกระชับเหมือนกับdict(x, **y)ไม่มีข้อเสีย
Carl Meyer

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

สำหรับรายการที่มีคีย์ในพจนานุกรมทั้งสอง ('b') คุณสามารถควบคุมได้ว่าจะให้ปลายอันใดของผลลัพธ์ออกมาโดยใส่อันสุดท้าย


ใน python 3 คุณจะได้รับ TypeError: ตัวถูกดำเนินการที่ไม่รองรับประเภทของ +: 'dict_items' และ 'dict_items' ... คุณควรสรุปแต่ละ dict ด้วย list () เช่น: dict (list (x.items ()) + (y.items ()))
เพียงแค่กล่าว

49

ในขณะที่คำถามได้รับการตอบแล้วหลายครั้งวิธีแก้ไขปัญหาอย่างง่ายนี้ยังไม่ปรากฏ

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

มันเร็วเท่ากับ z0 และ z2 ที่กล่าวถึงข้างต้น แต่เข้าใจง่ายและเปลี่ยนแปลงได้ง่าย


3
แต่มันเป็นสามแถลงการณ์มากกว่าหนึ่งนิพจน์
Fortran

14
ใช่ วิธีแก้ปัญหาแบบ one-expression-solution ที่กล่าวมานั้นช้าหรือชั่ว รหัสที่ดีสามารถอ่านได้และบำรุงรักษาได้ ดังนั้นปัญหาคือคำถามไม่ใช่คำตอบ เราควรถามทางออกที่ดีที่สุดของปัญหาไม่ใช่วิธีแก้ปัญหาหนึ่งบรรทัด
phobie

7
สูญเสียz4 = {}และเปลี่ยนบรรทัดถัดไปเป็นz4 = x.copy()- ดีกว่าแค่รหัสที่ดีไม่ได้ทำสิ่งที่ไม่จำเป็น (ซึ่งทำให้อ่านและบำรุงรักษาได้มากขึ้น)
ติโน

3
คำแนะนำของคุณจะเปลี่ยนเป็นคำตอบของ Matthews ในขณะที่คำตอบของเขาใช้ได้ แต่ฉันคิดว่าของฉันอ่านได้ง่ายและบำรุงรักษาได้ดีกว่า บรรทัดพิเศษจะไม่ดีถ้ามันจะเสียเวลาในการดำเนินการ
phobie

47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

ในบรรดาคำตอบที่ร่มรื่นและน่าสงสัยตัวอย่างที่ส่องแสงนี้เป็นหนทางเดียวที่ดีในการรวม dicts ใน Python รับรองโดยเผด็จการเพื่อชีวิตGuido van Rossum ! มีคนแนะนำอีกครึ่งหนึ่งของสิ่งนี้ แต่ไม่ได้ใส่ไว้ในฟังก์ชัน

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

ให้:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}

39

หากคุณคิดว่า lambdas นั้นชั่วร้ายอย่าอ่านต่อไป ตามที่ร้องขอคุณสามารถเขียนโซลูชันที่รวดเร็วและมีประสิทธิภาพของหน่วยความจำด้วยนิพจน์เดียว:

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}

ตามที่แนะนำข้างต้นการใช้สองบรรทัดหรือการเขียนฟังก์ชั่นน่าจะเป็นวิธีที่ดีกว่า


33

เป็นงูเหลือม ใช้ความเข้าใจ :

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

>>> print z
{'a': 1, 'c': 11, 'b': 10}

1
ในฐานะที่เป็นฟังก์ชั่น:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight

1
บันทึกการค้นหาโดยทำซ้ำคู่คีย์ / ค่าโดยตรง:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger

30

ใน python3 itemsเมธอดจะไม่ส่งคืนรายการอีกต่อไปแต่เป็นมุมมองซึ่งทำหน้าที่เหมือนชุด ในกรณีนี้คุณจะต้องใช้การรวมสหภาพเนื่องจากการต่อข้อมูล+จะไม่ทำงาน:

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

สำหรับพฤติกรรมที่คล้าย python3 ในเวอร์ชั่น 2.7 viewitemsวิธีการควรใช้แทนitems:

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

ฉันชอบสัญกรณ์นี้ต่อไปเพราะมันดูเป็นธรรมชาติมากกว่าที่จะคิดว่ามันเป็นการดำเนินการของสหภาพแรงงานมากกว่าที่จะต่อกัน

แก้ไข:

อีกสองสามคะแนนสำหรับงูหลาม 3 ก่อนอื่นให้จำไว้ว่าdict(x, **y)เคล็ดลับจะไม่ทำงานในงูหลาม 3 เว้นแต่ว่ามีกุญแจอยู่yนั้นเป็นสตริง

นอกจากนี้คำตอบ Chainmap ของ Raymond Hettinger's ก็สวยหรูเนื่องจากมันสามารถใช้จำนวน dicts เป็นข้อโต้แย้งได้ แต่จาก docsมันดูเหมือนว่ามันจะเรียงลำดับรายการ dicts ทั้งหมดสำหรับการค้นหาแต่ละครั้ง:

การค้นหาจะทำการค้นหาการแมปพื้นฐานอย่างต่อเนื่องจนกว่าจะพบกุญแจ

วิธีนี้อาจทำให้คุณช้าลงหากคุณมีการค้นหาจำนวนมากในแอปพลิเคชันของคุณ:

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

ดังนั้นลำดับความสำคัญจะช้าลงสำหรับการค้นหา ฉันเป็นแฟนของ Chainmap แต่ดูเป็นประโยชน์น้อยกว่าที่อาจมีการค้นหาจำนวนมาก


22

การใช้ผิดวิธีนำไปสู่โซลูชันแบบนิพจน์สำหรับคำตอบของ Matthew :

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

คุณบอกว่าคุณต้องการแสดงออกอย่างใดอย่างหนึ่งดังนั้นฉันจึงทำร้าย lambdaเพื่อผูกชื่อและทูเปิลเพื่อแทนที่ขีด จำกัด การแสดงออกเพียงครั้งเดียวของแลมบ์ดา รู้สึกฟรีเพื่อประจบประแจง

คุณสามารถทำสิ่งนี้ได้แน่นอนหากคุณไม่สนใจการทำสำเนา:

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

22

วิธีง่ายๆในการใช้ itertools ที่รักษาลำดับ (dicts หลังมีความสำคัญกว่า)

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

และมันคือการใช้งาน:

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


16

แม้ว่าคำตอบจะดีสำหรับพจนานุกรมแบบตื้นนี้แต่ไม่มีวิธีการใดที่กำหนดไว้ที่นี่จริง ๆ แล้วผสานพจนานุกรมแบบลึกเข้าด้วยกัน

ตัวอย่างดังต่อไปนี้:

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

อย่างใดอย่างหนึ่งคาดว่าผลลัพธ์ของสิ่งนี้:

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

เราได้สิ่งนี้มาแทน:

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

รายการ 'หนึ่ง' ควรมี 'depth_2' และ 'พิเศษ' เป็นรายการในพจนานุกรมถ้ามันเป็นการผสานอย่างแท้จริง

ใช้โซ่ยังไม่ทำงาน:

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

ผลลัพธ์ใน:

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

การผสานอย่างลึกที่ rcwesick ให้ไว้ก็สร้างผลลัพธ์เดียวกัน

ใช่มันจะทำงานเพื่อรวมพจนานุกรมตัวอย่าง แต่ไม่มีพวกเขาใด ๆ ที่เป็นกลไกทั่วไปที่จะผสาน ฉันจะอัปเดตในภายหลังเมื่อฉันเขียนวิธีการที่ผสานจริง


11

(สำหรับ Python2.7 * เท่านั้นมีวิธีที่ง่ายกว่าสำหรับ Python3 *)

หากคุณไม่ชอบการนำเข้าโมดูลไลบรารีมาตรฐานคุณสามารถทำได้

from functools import reduce

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

( จำเป็นต้องใช้or aบิตในlambdaเพราะdict.updateจะได้รับผลตอบแทนเสมอNone)


11

หากคุณไม่ทราบกรรมวิธีx,

x.update(y) or x

ง่ายอ่านง่ายนักแสดง คุณรู้ว่า update()ผลตอบแทนเสมอNoneซึ่งเป็นค่าเท็จ ดังนั้นนิพจน์ด้านบนจะประเมินเป็นเสมอxหลังจากอัปเดต

วิธีการกลายพันธุ์ในไลบรารีมาตรฐาน (เช่น.update()) ส่งคืนNoneโดยการประชุมดังนั้นรูปแบบนี้จะใช้ได้กับวิธีเหล่านั้นด้วย หากคุณกำลังใช้วิธีที่ไม่เป็นไปตามอนุสัญญานี้orอาจใช้ไม่ได้ แต่คุณสามารถใช้ tuple display และ index เพื่อทำให้มันเป็น expression เดียวแทน สิ่งนี้ใช้ได้ผลไม่ว่าองค์ประกอบแรกจะประเมินเป็นอย่างไร

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

หากคุณยังไม่มีxตัวแปรคุณสามารถใช้lambdaเพื่อสร้างท้องถิ่นโดยไม่ต้องใช้คำสั่งกำหนด จำนวนนี้จะใช้lambdaเป็นนิพจน์ให้ซึ่งเป็นเทคนิคทั่วไปในภาษาที่ใช้งานได้ แต่อาจไม่ไพเราะ

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

แม้ว่ามันจะไม่แตกต่างจากการใช้ตัวดำเนินการวอลรัสต่อไปนี้ (Python 3.8+ เท่านั้น):

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

ถ้าคุณทำต้องการสำเนา PEP 448 {**x, **y}รูปแบบที่ง่ายที่สุด แต่ถ้ามันไม่สามารถใช้ได้ในเวอร์ชั่น Python ของคุณ (เก่ากว่า) รูปแบบการปล่อยให้ทำงานได้ที่นี่เช่นกัน

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

(นั่นคือแน่นอนเทียบเท่ากับ(z := x.copy()).update(y) or zแต่ถ้าเวอร์ชัน Python ของคุณใหม่เพียงพอสำหรับสิ่งนั้นสไตล์ PEP 448 จะพร้อมใช้งาน)


10

วาดจากแนวคิดที่นี่และที่อื่น ๆ ฉันได้เข้าใจฟังก์ชั่น:

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

การใช้งาน (ทดสอบในหลาม 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})

คุณสามารถใช้แลมบ์ดาแทนได้


10

ปัญหาที่ฉันมีกับวิธีแก้ปัญหาที่ระบุไว้ในปัจจุบันคือในพจนานุกรมที่ผสานค่าสำหรับคีย์ "b" คือ 10 แต่สำหรับวิธีการคิดของฉันมันควรเป็น 12 ในแง่นั้นฉันจะแสดงสิ่งต่อไปนี้:

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

ผล:

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}

1
คุณอาจสนใจcytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli

10

มันโง่มากที่.updateไม่ได้อะไรเลย
ฉันแค่ใช้ฟังก์ชั่นตัวช่วยง่ายๆในการแก้ปัญหา:

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

ตัวอย่าง:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

วิธีนี้จะช่วยแก้ปัญหาของคุณได้


9

ซึ่งสามารถทำได้ด้วยความเข้าใจ dict เดียว:

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

ในมุมมองของฉันคำตอบที่ดีที่สุดสำหรับส่วน 'นิพจน์เดียว' เนื่องจากไม่จำเป็นต้องใช้ฟังก์ชั่นพิเศษและสั้น


ฉันสงสัยว่าการแสดงจะไม่ดีมาก การสร้างชุดของแต่ละ dict จากนั้นทำซ้ำผ่านคีย์เท่านั้นหมายถึงการค้นหาค่าในแต่ละครั้ง (แม้ว่าจะค่อนข้างเร็ว แต่ยังเพิ่มลำดับของฟังก์ชันสำหรับการปรับสเกล)
Breezer

2
ทุกอย่างขึ้นอยู่กับเวอร์ชั่นของไพ ธ อนที่เราใช้อยู่ ใน 3.5 และสูงกว่า {** x, ** y} ให้พจนานุกรมที่ต่อกัน
Rashid Mv

9

จะมีตัวเลือกใหม่เมื่องูหลาม 3.8 รุ่น ( ที่กำหนดไว้สำหรับ 20 ตุลาคม 2019 ) ขอบคุณPEP 572: นิพจน์การกำหนด ตัวดำเนินการนิพจน์การกำหนดค่าใหม่:=ช่วยให้คุณสามารถกำหนดผลลัพธ์ของcopyและยังคงใช้ในการโทรupdateโดยปล่อยให้นิพจน์เดียวรวมเป็นนิพจน์เดียวแทนที่จะเป็นสองคำสั่งเปลี่ยน:

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

ถึง:

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

ขณะเดียวกันก็ประพฤติตัวเหมือนกันทุกประการ หากคุณต้องส่งคืนผลลัพธ์dict(คุณขอให้นิพจน์ส่งคืนdict; ข้างต้นสร้างและกำหนดให้newdictแต่ไม่ส่งคืนดังนั้นคุณจึงไม่สามารถใช้มันเพื่อส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชันตามที่เป็นอยู่myfunc((newdict := dict1.copy()).update(dict2))) จากนั้นเพียงเพิ่มor newdictไปยังจุดสิ้นสุด (เนื่องจากupdateผลตอบแทนNoneซึ่งเป็นเท็จก็จะประเมินและกลับมาnewdictเป็นผลมาจากการแสดงออก):

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

ข้อแม้ที่สำคัญ:โดยทั่วไปฉันไม่แนะนำให้ใช้วิธีนี้:

newdict = {**dict1, **dict2}

วิธีการเปิดกล่องบรรจุนั้นชัดเจนกว่า (สำหรับทุกคนที่รู้เกี่ยวกับการเปิดกล่องบรรจุทั่วไปในที่แรกที่คุณควรทำ ) ไม่ต้องการชื่อสำหรับผลลัพธ์เลย (ดังนั้นจึงกระชับกว่าเมื่อสร้างชั่วคราวที่ส่งผ่านไปยัง ฟังก์ชั่นหรือรวมอยู่ในlist/ tupleตัวอักษรหรือสิ่งที่คล้ายกัน) และเกือบจะเร็วขึ้นเช่นกันการอยู่บน CPython ประมาณคร่าว ๆ

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

แต่ทำที่ชั้น C โดยใช้dictAPI คอนกรีตดังนั้นจึงไม่มีการค้นหา / เชื่อมโยงวิธีการแบบไดนามิกหรือค่าใช้จ่ายการจัดส่งการเรียกใช้ฟังก์ชันที่เกี่ยวข้อง (ซึ่ง(newdict := dict1.copy()).update(dict2)หลีกเลี่ยงไม่ได้เหมือนกับต้นฉบับสองซับในพฤติกรรม / binding / การเรียกใช้เมธอด

นอกจากนี้ยังสามารถขยายได้มากขึ้นเนื่องจากการรวมสามdicts นั้นชัดเจน:

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

เมื่อใช้การมอบหมายการแสดงออกจะไม่ปรับขนาดเช่นนั้น ที่ใกล้ที่สุดที่คุณจะได้รับคือ:

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

หรือไม่มี tuple ชั่วคราวของNones แต่ด้วยการทดสอบความเป็นจริงของNoneผลลัพธ์แต่ละรายการ:

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

อย่างใดอย่างหนึ่งซึ่งเป็นที่ไม่สวยงามเท่าที่เห็นได้ชัดมากและรวมถึงความไร้ประสิทธิภาพต่อไป (อย่างใดอย่างหนึ่งที่สูญเสียไปชั่วคราวtupleของNones สำหรับการแยกจุลภาคหรือ truthiness ไม่มีจุดหมายการทดสอบของแต่ละupdate's Noneตอบแทนสำหรับorแยก)

ข้อได้เปรียบที่แท้จริงเพียงอย่างเดียวของวิธีการแสดงออกของการมอบหมายจะเกิดขึ้นหาก:

  1. คุณมีรหัสทั่วไปที่ต้องการจัดการทั้งsets และdicts (ทั้งคู่สนับสนุนcopyและupdateดังนั้นรหัสทำงานตามที่คุณคาดหวังไว้)
  2. คุณคาดหวังที่จะได้รับโดยพล Dict เหมือนวัตถุที่ไม่ใช่แค่dictตัวเองและต้องรักษาชนิดและความหมายของด้านซ้ายมือ (แทนที่จะสิ้นสุดกับธรรมดาdict) ในขณะที่myspecialdict({**speciala, **specialb})อาจใช้งานได้จะเกี่ยวข้องกับการเพิ่มชั่วคราวdictและถ้าmyspecialdictมีคุณสมบัติdictไม่สามารถรักษาไว้ได้ (เช่นdictตอนนี้ s รักษาคำสั่งปกติตามการปรากฏตัวครั้งแรกของคีย์และค่าตามการปรากฏตัวครั้งสุดท้ายของคีย์คุณอาจต้องการ คำสั่งที่รักษาลำดับตามสุดท้ายลักษณะที่ปรากฏของคีย์ดังนั้นการอัพเดตค่าจะย้ายไปยังจุดสิ้นสุด) จากนั้นความหมายจะผิด เนื่องจากเวอร์ชันนิพจน์ที่ได้รับมอบหมายใช้เมธอดที่มีชื่อ (ซึ่งมีการคาดการณ์มากเกินไปว่าจะทำงานได้อย่างเหมาะสม) จึงไม่สร้างdictเลย (เว้นแต่ว่าdict1เป็น a dict), รักษาประเภทดั้งเดิม (และความหมายของประเภทดั้งเดิม) ทั้งหมดในขณะที่หลีกเลี่ยงชั่วคราวใด ๆ

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}

วิธีนี้จะเขียนทับxด้วยสำเนา ถ้าxเป็นอาร์กิวเมนต์ของฟังก์ชันสิ่งนี้จะไม่ทำงาน (ดูตัวอย่าง )
bartolo-otrit
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.