Làm cách nào để hợp nhất hai từ điển trong một biểu thức trong Python?


4786

Tôi có hai từ điển Python và tôi muốn viết một biểu thức trả về hai từ điển này, được hợp nhất. Các update()phương pháp sẽ là những gì tôi cần, nếu nó trở lại kết quả của nó thay vì sửa đổi một cuốn từ điển tại chỗ.

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

Làm thế nào tôi có thể có được từ điển hợp nhất cuối cùng trong z, không x?

(Nói rõ hơn, cách xử lý xung đột một lần cuối cùng dict.update()là điều tôi đang tìm kiếm.)


1
Nếu bạn đang sử dụng Python 3.9 alpha, hãy sử dụngz = x | y
The Daleks

Câu trả lời:


5703

Làm cách nào để hợp nhất hai từ điển Python trong một biểu thức?

Đối với từ điển xy, ztrở thành một từ điển được hợp nhất nông với các giá trị ythay thế từ x.

  • Trong Python 3.5 trở lên:

    z = {**x, **y}
  • Trong Python 2, (hoặc 3,4 hoặc thấp hơn) viết một hàm:

    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
    

    và bây giờ:

    z = merge_two_dicts(x, y)
  • Trong Python 3.9.0a4 trở lên (ngày phát hành cuối cùng khoảng tháng 10 năm 2020): PEP-584 , được thảo luận ở đây , đã được triển khai để đơn giản hóa hơn nữa điều này:

    z = x | y          # NOTE: 3.9+ ONLY

Giải trình

Giả sử bạn có hai dicts và bạn muốn hợp nhất chúng thành một dict mới mà không thay đổi các dicts gốc:

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

Kết quả mong muốn là có được một từ điển mới ( z) với các giá trị được hợp nhất và các giá trị của chính tả thứ hai ghi đè lên các từ điển đầu tiên.

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

Một cú pháp mới cho điều này, được đề xuất trong PEP 448có sẵn từ Python 3.5 , là

z = {**x, **y}

Và nó thực sự là một biểu thức duy nhất.

Lưu ý rằng chúng ta cũng có thể hợp nhất với ký hiệu bằng chữ:

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

và bây giờ:

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

Hiện tại nó đang được hiển thị như được triển khai trong lịch phát hành cho 3.5, PEP 478 và hiện đã được đưa vào tài liệu What's New trong Python 3.5 .

Tuy nhiên, vì nhiều tổ chức vẫn còn trên Python 2, bạn có thể muốn làm điều này theo cách tương thích ngược. Cách Pythonic kinh điển, có sẵn trong Python 2 và Python 3.0-3.4, là thực hiện điều này như một quy trình gồm hai bước:

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

Trong cả hai phương pháp, ysẽ đến lần thứ hai và các giá trị của nó sẽ thay thế xcác giá trị của nó, do đó 'b'sẽ chỉ ra 3trong kết quả cuối cùng của chúng tôi.

Chưa có trên Python 3.5, nhưng muốn có một biểu thức

Nếu bạn chưa có trên Python 3.5 hoặc cần viết mã tương thích ngược và bạn muốn điều này trong một biểu thức duy nhất , cách hiệu quả nhất trong khi cách tiếp cận đúng là đặt nó vào một hàm:

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

và sau đó bạn có một biểu thức duy nhất:

z = merge_two_dicts(x, y)

Bạn cũng có thể tạo một hàm để hợp nhất một số lượng không xác định, từ 0 đến một số rất lớn:

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

Hàm này sẽ hoạt động trong Python 2 và 3 cho tất cả các dicts. ví dụ: đưa ra các dấu hiệu acho g:

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

và các cặp giá trị chính gsẽ được ưu tiên hơn so với các mệnh ađề f, v.v.

Phê bình các câu trả lời khác

Đừng sử dụng những gì bạn thấy trong câu trả lời được chấp nhận trước đây:

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

Trong Python 2, bạn tạo hai danh sách trong bộ nhớ cho mỗi dict, tạo danh sách thứ ba trong bộ nhớ với độ dài bằng với độ dài của hai danh sách đầu tiên được đặt cùng nhau, sau đó loại bỏ cả ba danh sách để tạo ra dict. Trong Python 3, điều này sẽ thất bại vì bạn thêm hai dict_itemsđối tượng lại với nhau chứ không phải hai danh sách -

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

và bạn sẽ phải tạo chúng một cách rõ ràng dưới dạng danh sách, ví dụ z = dict(list(x.items()) + list(y.items())). Đây là một sự lãng phí tài nguyên và sức mạnh tính toán.

Tương tự, việc kết hợp items()trong Python 3 ( viewitems()trong Python 2.7) cũng sẽ thất bại khi các giá trị là các đối tượng không thể xóa được (ví dụ như danh sách). Ngay cả khi các giá trị của bạn có thể được băm, vì các tập hợp không được sắp xếp theo ngữ nghĩa, hành vi không được xác định liên quan đến quyền ưu tiên. Vì vậy, đừng làm điều này:

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

Ví dụ này cho thấy những gì xảy ra khi các giá trị không thể thực hiện được:

>>> 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 là một ví dụ trong đó y nên được ưu tiên, nhưng thay vào đó, giá trị từ x được giữ lại do thứ tự các tập hợp tùy ý:

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

Một hack khác bạn không nên sử dụng:

z = dict(x, **y)

Điều này sử dụng hàm dicttạo, và rất nhanh và hiệu quả bộ nhớ (thậm chí nhiều hơn một chút so với quy trình hai bước của chúng tôi) nhưng trừ khi bạn biết chính xác điều gì đang xảy ra ở đây (nghĩa là, lệnh thứ hai đang được truyền dưới dạng đối số từ khóa cho dict constructor), rất khó đọc, nó không phải là mục đích sử dụng, và vì vậy nó không phải là Pythonic.

Đây là một ví dụ về việc sử dụng đang được khắc phục trong django .

Các ký tự được dự định để lấy các khóa có thể băm (ví dụ: fro chục hoặc bộ dữ liệu), nhưng phương pháp này không thành công trong Python 3 khi các khóa không phải là chuỗi.

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

Từ danh sách gửi thư , Guido van Rossum, người tạo ra ngôn ngữ, đã viết:

Tôi ổn với việc tuyên bố dict ({}, ** {1: 3}) là bất hợp pháp, vì sau tất cả, đó là lạm dụng cơ chế **.

Rõ ràng dict (x, ** y) đang diễn ra như "hack mát" cho "gọi x.update (y) và trả lại x". Cá nhân tôi thấy nó đáng khinh hơn là ngầu.

Theo sự hiểu biết của tôi (cũng như sự hiểu biết của người tạo ra ngôn ngữ ) rằng việc sử dụng dự định dict(**y)là để tạo ra các mục đích cho mục đích dễ đọc, ví dụ:

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

thay vì

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

Phản hồi ý kiến

Bất chấp những gì Guido nói, dict(x, **y)phù hợp với đặc tả chính tả, btw. hoạt động cho cả Python 2 và 3. Thực tế là điều này chỉ hoạt động đối với các khóa chuỗi là kết quả trực tiếp của cách các tham số từ khóa hoạt động và không phải là một lệnh ngắn. Cũng không sử dụng toán tử ** ở nơi này lạm dụng cơ chế, trên thực tế ** được thiết kế chính xác để chuyển các ký tự làm từ khóa.

Một lần nữa, nó không hoạt động trong 3 khi các khóa không phải là chuỗi. Hợp đồng gọi ngầm là các không gian tên lấy các ký tự thông thường, trong khi người dùng chỉ phải truyền các đối số từ khóa là các chuỗi. Tất cả các cuộc gọi khác thi hành nó. dictđã phá vỡ tính nhất quán này trong 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}

Sự không nhất quán này là không tốt khi đưa ra các triển khai khác của Python (Pypy, Jython, IronPython). Do đó, nó đã được sửa trong Python 3, vì việc sử dụng này có thể là một thay đổi đột phá.

Tôi gửi cho bạn rằng đó là sự bất tài độc hại khi cố tình viết mã chỉ hoạt động trong một phiên bản của ngôn ngữ hoặc chỉ hoạt động khi có một số ràng buộc tùy ý nhất định.

Thêm ý kiến:

dict(x.items() + y.items()) vẫn là giải pháp dễ đọc nhất cho Python 2. Tính dễ đọc.

Phản ứng của tôi: merge_two_dicts(x, y)thực sự có vẻ rõ ràng hơn nhiều đối với tôi, nếu chúng ta thực sự quan tâm đến khả năng đọc. Và nó không tương thích về phía trước, vì Python 2 ngày càng không được dùng nữa.

{**x, **y}dường như không xử lý các từ điển lồng nhau. nội dung của các khóa lồng nhau chỉ đơn giản là bị ghi đè, không được hợp nhất [...] Cuối cùng tôi đã bị đốt cháy bởi những câu trả lời không hợp nhất theo cách đệ quy và tôi đã ngạc nhiên khi không ai nhắc đến nó. Theo cách giải thích của tôi về từ "hợp nhất", những câu trả lời này mô tả "cập nhật từ này sang lệnh khác" và không hợp nhất.

Đúng. Tôi phải giới thiệu bạn trở lại câu hỏi, đó là yêu cầu cho một nông hợp nhất của hai từ điển, với các giá trị của đầu tiên là ghi đè bởi các nhân thứ hai - trong một biểu thức duy nhất.

Giả sử hai từ điển từ điển, người ta có thể hợp nhất đệ quy chúng trong một hàm duy nhất, nhưng bạn nên cẩn thận không sửa đổi các từ trong một nguồn, và cách chắc chắn nhất để tránh đó là tạo một bản sao khi gán giá trị. Vì các khóa phải được băm và do đó thường không thay đổi, nên việc sao chép chúng là vô nghĩa:

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

Sử dụng:

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

Đến với các tình huống dự phòng cho các loại giá trị khác vượt xa phạm vi của câu hỏi này, vì vậy tôi sẽ chỉ cho bạn câu trả lời của tôi cho câu hỏi kinh điển về "Từ điển hợp nhất từ ​​điển" .

Ít biểu diễn hơn nhưng Ad-hocs đúng

Những cách tiếp cận này ít hiệu quả hơn, nhưng chúng sẽ cung cấp hành vi chính xác. Họ sẽ ít hơn nhiều performant hơn copyupdatehay giải nén mới, vì họ lặp qua từng cặp khóa-giá trị ở mức trừu tượng cao hơn, nhưng họ làm tôn trọng thứ tự ưu tiên (dicts sau có ưu tiên)

Bạn cũng có thể xâu chuỗi các dicts bằng tay bên trong một cách hiểu chính tả:

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

hoặc trong python 2.6 (và có lẽ sớm nhất là 2.4 khi các biểu thức trình tạo được giới thiệu):

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

itertools.chain sẽ xâu chuỗi các vòng lặp qua các cặp khóa-giá trị theo đúng thứ tự:

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

Phân tích hiệu suất

Tôi sẽ chỉ thực hiện phân tích hiệu suất của các tập quán được biết là hành xử chính xác.

import timeit

Sau đây được thực hiện trên Ubuntu 14.04

Trong Python 2.7 (hệ thống 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

Trong Python 3.5 (PP giả):

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

Tài nguyên về từ điển


9
@MohammadAzim "chỉ chuỗi" chỉ áp dụng cho mở rộng đối số từ khóa trong các lệnh gọi, không phải là cú pháp giải nén tổng quát. Để chứng minh rằng điều này hoạt động: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall

37
câu trả lời ngắn như z = {**x, **y}thực sự kích thích tôi
pcko1

1
Điều này có thể được thay đổi khi PEP-0584 được chấp nhận. Một nhà điều hành công đoàn mới sẽ được thực hiện với cú pháp sau:x | y
Callam Delaney

2
Khi một câu trả lời cần một bản tóm tắt ở đầu thì nó quá dài.
Gringo Suave

2
Xin chào, đầu trang là một bản tóm tắt, vâng. Tùy bạn Toàn bộ điều sẽ là một bài viết blog tuyệt vời. Lưu ý Py 3.4 trở xuống là EOL, 3.5 tiếp cận EOL vào năm 2020-09.
Gringo Suave

1617

Trong trường hợp của bạn, những gì bạn có thể làm là:

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

Điều này, như bạn muốn, sẽ đưa dict cuối cùng vào zvà làm cho giá trị của khóa bbị ghi đè chính xác bởi ygiá trị của dict thứ hai ( ):

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

Nếu bạn sử dụng Python 3, nó chỉ phức tạp hơn một chút. Để tạo z:

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

Nếu bạn sử dụng Python phiên bản 3.9.0a4 trở lên, thì bạn có thể trực tiếp sử dụng:

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

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

3
Đừng sử dụng cái này vì nó rất kém hiệu quả. (Xem kết quả thời gian bên dưới.) Có thể cần thiết trong Py2 ngày nếu chức năng trình bao bọc không phải là một tùy chọn, nhưng những ngày đó đã qua.
Gringo Suave

632

Một thay thế:

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

83
Để làm rõ lý do tại sao điều này không đáp ứng tiêu chí được cung cấp bởi câu hỏi: đó không phải là một biểu thức duy nhất và nó không trả về z.
Alex

2
@neuronet mỗi oneliner thường chỉ là mã di chuyển phải xảy ra thành một thành phần khác và giải quyết nó ở đó. đây chắc chắn là một trong những trường hợp nhưng các ngôn ngữ khác có cấu trúc đẹp hơn python cho việc này. và có một biến thể trong suốt tham chiếu trả về phần tử của nó là một điều tốt đẹp để có.
Alex

12
Đặt nó theo cách này: nếu bạn cần đặt hai dòng bình luận giải thích một dòng mã của bạn cho những người bạn đưa mã của bạn cho ... bạn đã thực sự làm nó trong một dòng chưa? :) Tôi hoàn toàn đồng ý Python không tốt cho việc này: nên có một cách dễ dàng hơn nhiều. Trong khi câu trả lời này là nhiều pythonic, nó thực sự là tất cả những gì rõ ràng hoặc rõ ràng? Updatekhông phải là một trong những chức năng "cốt lõi" mà mọi người có xu hướng sử dụng nhiều.
eric

Chà, nếu mọi người khăng khăng biến nó thành một oneliner, bạn luôn có thể làm (lambda z: z.update(y) or z)(x.copy()): P
Towr

341

Một lựa chọn khác, ngắn gọn hơn:

z = dict(x, **y)

Lưu ý : đây đã trở thành một câu trả lời phổ biến, nhưng điều quan trọng là chỉ ra rằng nếu ycó bất kỳ khóa không phải chuỗi nào, thì thực tế là nó hoạt động hoàn toàn là lạm dụng chi tiết triển khai CPython và nó không hoạt động trong Python 3, hoặc trong PyPy, IronPython hoặc Jython. Ngoài ra, Guido không phải là một fan hâm mộ . Vì vậy, tôi không thể đề xuất kỹ thuật này cho mã di động tương thích hoặc triển khai chéo, điều này thực sự có nghĩa là nên tránh hoàn toàn.


Hoạt động tốt trong Python 3 và PyPy và PyPy 3 , không thể nói chuyện với Jython hoặc Iron. Cho mẫu này được ghi lại rõ ràng (xem biểu mẫu hàm tạo thứ ba trong tài liệu này) Tôi cho rằng đó không phải là "chi tiết triển khai" mà là sử dụng tính năng có chủ ý.
amcgregor

5
@amcgregor Bạn đã bỏ lỡ cụm từ khóa "nếu y có bất kỳ khóa không phải chuỗi nào." Đó là những gì không hoạt động trong Python3; thực tế là nó hoạt động trong CPython 2 là một chi tiết triển khai không thể dựa vào. IFF tất cả các khóa của bạn được đảm bảo là chuỗi, đây là một tùy chọn được hỗ trợ đầy đủ.
Carl Meyer

214

Đây có lẽ sẽ không phải là một câu trả lời phổ biến, nhưng bạn gần như chắc chắn không muốn làm điều này. Nếu bạn muốn một bản sao hợp nhất, sau đó sử dụng bản sao (hoặc deepcopy , tùy thuộc vào những gì bạn muốn) và sau đó cập nhật. Hai dòng mã dễ đọc hơn nhiều - nhiều Pythonic hơn so với việc tạo dòng đơn với .items () + .items (). Rõ ràng là tốt hơn so với ngầm.

Ngoài ra, khi bạn sử dụng .items () (trước Python 3.0), bạn đang tạo một danh sách mới có chứa các mục từ dict. Nếu từ điển của bạn lớn, thì đó là khá nhiều chi phí (hai danh sách lớn sẽ bị loại bỏ ngay khi từ điển được hợp nhất được tạo ra). update () có thể hoạt động hiệu quả hơn, bởi vì nó có thể chạy qua từng mục chính thứ hai.

Về thời gian :

>>> 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 sự chậm lại nhỏ giữa hai lần đầu tiên là đáng giá cho khả năng đọc. Ngoài ra, các đối số từ khóa để tạo từ điển chỉ được thêm vào Python 2.3, trong khi copy () và update () sẽ hoạt động trong các phiên bản cũ hơn.


150

Trong câu trả lời tiếp theo, bạn đã hỏi về hiệu suất tương đối của hai phương án này:

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

Trên máy của tôi, ít nhất (một x86_64 khá bình thường chạy Python 2.5.2), sự thay thế z2không chỉ ngắn hơn và đơn giản hơn mà còn nhanh hơn đáng kể. Bạn có thể tự xác minh điều này bằng cách sử dụng timeitmô-đun đi kèm với Python.

Ví dụ 1: từ điển giống hệt nhau ánh xạ 20 số nguyên liên tiếp với chính chúng:

% 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

z2thắng theo hệ số 3,5 hoặc hơn. Các từ điển khác nhau dường như mang lại kết quả khá khác nhau, nhưng z2dường như luôn luôn đi ra phía trước. (Nếu bạn nhận được kết quả không nhất quán cho cùng một bài kiểm tra, hãy thử chuyển qua -rvới một số lớn hơn số mặc định 3.)

Ví dụ 2: từ điển không chồng chéo ánh xạ 252 chuỗi ngắn sang số nguyên và ngược lại:

% 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 thắng khoảng 10 lần. Đó là một chiến thắng khá lớn trong cuốn sách của tôi!

Sau khi so sánh hai thứ đó, tôi tự hỏi liệu z1hiệu suất kém có thể được quy cho chi phí xây dựng hai danh sách vật phẩm hay không, điều này khiến tôi tự hỏi liệu biến thể này có thể hoạt động tốt hơn không:

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

Một vài bài kiểm tra nhanh, vd

% 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

dẫn tôi đến kết luận rằng nó z3có phần nhanh hơn z1, nhưng không nhanh bằng z2. Chắc chắn không có giá trị tất cả các gõ thêm.

Cuộc thảo luận này vẫn còn thiếu một điều quan trọng, đó là so sánh hiệu suất của các lựa chọn thay thế này với cách hợp nhất "rõ ràng" để hợp nhất hai danh sách: sử dụng updatephương pháp. Để cố gắng giữ mọi thứ ngang bằng với các biểu thức, không ai trong số đó sửa đổi x hoặc y, tôi sẽ tạo một bản sao của x thay vì sửa đổi tại chỗ, như sau:

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

Một kết quả điển hình:

% 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

Nói cách khác, z0z2dường như có hiệu suất cơ bản giống hệt nhau. Bạn có nghĩ rằng đây có thể là một sự trùng hợp? Tôi không....

Trên thực tế, tôi đã đi xa hơn khi tuyên bố rằng mã Python thuần túy không thể làm tốt hơn điều này. Và nếu bạn có thể làm tốt hơn đáng kể trong mô-đun mở rộng C, tôi tưởng tượng mọi người Python có thể quan tâm đến việc kết hợp mã của bạn (hoặc một biến thể trong cách tiếp cận của bạn) vào lõi Python. Python sử dụng dictở nhiều nơi; tối ưu hóa hoạt động của nó là một vấn đề lớn.

Bạn cũng có thể viết cái này như

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

như Tony làm, nhưng (không ngạc nhiên) sự khác biệt trong ký hiệu hóa ra không có bất kỳ ảnh hưởng nào có thể đo lường được đối với hiệu suất. Sử dụng bất cứ điều gì có vẻ đúng với bạn. Tất nhiên, anh ấy hoàn toàn chính xác để chỉ ra rằng phiên bản hai câu dễ hiểu hơn nhiều.


5
Điều này không hoạt động trong Python 3; items()không thể catenable, và iteritemskhông tồn tại.
Antti Haapala

127

Trong Python 3.0 trở lên , bạn có thể sử dụng collections.ChainMapnhóm nào nhiều dicts hoặc ánh xạ khác với nhau để tạo một chế độ xem có thể cập nhật duy nhất:

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

Cập nhật cho Python 3.5 trở lên : Bạn có thể sử dụng đóng gói và giải nén từ điển mở rộng PEP 448 . Điều này nhanh chóng và dễ dàng:

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

3
Nhưng mọi người nên thận trọng khi sử dụng ChainMap, có một lưu ý rằng nếu bạn có các khóa trùng lặp, các giá trị từ ánh xạ đầu tiên sẽ được sử dụng và khi bạn gọi, hãy delnói ChainMap c sẽ xóa ánh xạ đầu tiên của khóa đó.
Kẻ giết người

7
@Prerit Bạn còn mong đợi điều gì nữa không? Đó là cách bình thường xâu chuỗi không gian tên làm việc. Hãy xem xét cách $ PATH hoạt động trong bash. Xóa một thực thi trên đường dẫn không loại trừ một thực thi khác có cùng tên ngược dòng.
Raymond Hettinger

2
@Raymond Hettinger Tôi đồng ý, chỉ cần thêm một sự thận trọng. Hầu hết mọi người có thể không biết về nó. : D
Kẻ giết người

@Prerit Bạn có thể sử dụng để dicttránh điều đó, tức là:dict(ChainMap({}, y, x))
wjandrea

113

Tôi muốn một cái gì đó tương tự, nhưng với khả năng chỉ định cách các giá trị trên các khóa trùng lặp được hợp nhất, vì vậy tôi đã hack nó (nhưng không kiểm tra nhiều về nó). Rõ ràng đây không phải là một biểu thức đơn lẻ, mà nó là một lệnh gọi hàm duy nhất.

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

Đệ quy / cập nhật sâu một lệnh

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

Trình diễn:

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

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

print deepupdate(pluto_original, pluto_update)

Đầu ra:

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

Cảm ơn rednaw cho chỉnh sửa.


1
Điều này không trả lời câu hỏi. Câu hỏi rõ ràng yêu cầu một từ điển mới, z, từ các từ điển gốc, x và y, với các giá trị từ y thay thế các từ điển của x - không phải là một từ điển cập nhật. Câu trả lời này sửa đổi y tại chỗ bằng cách thêm các giá trị từ x. Tồi tệ hơn, nó không sao chép các giá trị này, vì vậy người ta có thể sửa đổi thêm từ điển đã sửa đổi, y và sửa đổi có thể được phản ánh trong từ điển x. @ Jérôme Tôi hy vọng mã này không gây ra bất kỳ lỗi nào cho ứng dụng của bạn - ít nhất là xem xét sử dụng deepcopy để sao chép các giá trị.
Aaron Hall

1
@AaronHall đồng ý điều này không trả lời câu hỏi. Nhưng nó đáp ứng nhu cầu của tôi. Tôi hiểu những hạn chế đó, nhưng đó không phải là vấn đề trong trường hợp của tôi. Nghĩ về nó, có thể cái tên này là sai lệch, vì nó có thể gợi lên một bản đồ sâu, mà nó không cung cấp. Nhưng nó giải quyết việc làm tổ sâu. Đây là một triển khai khác từ Martellibot: stackoverflow.com/questions/3232943/ .
Jérôme

72

Phiên bản tốt nhất tôi có thể nghĩ trong khi không sử dụng bản sao sẽ là:

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

Nó nhanh hơn dict(x.items() + y.items())nhưng không nhanh bằng n = copy(a); n.update(b), ít nhất là trên CPython. Phiên bản này cũng hoạt động trong Python 3 nếu bạn thay đổi iteritems()thành items()công cụ 2to3 tự động thực hiện.

Cá nhân tôi thích phiên bản này nhất vì nó mô tả khá tốt những gì tôi muốn trong một cú pháp chức năng duy nhất. Vấn đề nhỏ duy nhất là nó không hoàn toàn rõ ràng rằng các giá trị từ y được ưu tiên hơn các giá trị từ x, nhưng tôi không tin rằng thật khó để tìm ra điều đó.


71

Python 3.5 (PEP 448) cho phép tùy chọn cú pháp đẹp hơn:

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

Hoặc thậm chí

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

Trong Python 3.9 bạn cũng sử dụng | và | = với ví dụ dưới đây từ PEP 584

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

Bằng cách nào thì giải pháp này tốt hơn giải pháp dict(x, **y)? Như bạn (@CarlMeyer) đã đề cập trong phần ghi chú câu trả lời của riêng bạn ( stackoverflow.com/a/39858/2798610 ) Guido coi giải pháp đó là bất hợp pháp .
Blackeagle52

14
Guido không thích dict(x, **y)vì lý do (rất tốt) rằng nó chỉ dựa vào yviệc có các khóa là tên đối số từ khóa hợp lệ (trừ khi bạn đang sử dụng CPython 2.7, trong đó trình xây dựng dict gian lận). Sự phản đối / hạn chế này không áp dụng cho PEP 448, vốn khái quát **cú pháp giải nén để đọc chính tả. Vì vậy, giải pháp này có cùng cách xử lý dict(x, **y), không có nhược điểm.
Carl Meyer

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

Đối với các mục có khóa trong cả hai từ điển ('b'), bạn có thể kiểm soát mục nào kết thúc ở đầu ra bằng cách đặt mục đó vào cuối.


Trong python 3, bạn sẽ nhận được TypeError: loại toán hạng không được hỗ trợ cho +: 'dict_items' và 'dict_items' ... bạn nên gói gọn mỗi dict với list () như: dict (list (x.items ()) + list (y.items ()))
vừa được trả lời

49

Trong khi câu hỏi đã được trả lời nhiều lần, giải pháp đơn giản cho vấn đề này vẫn chưa được liệt kê.

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

Nó nhanh như z0 và z2 ác đã đề cập ở trên, nhưng dễ hiểu và thay đổi.


3
nhưng đó là ba tuyên bố chứ không phải một biểu thức
fortran

14
Đúng! Các giải pháp một biểu thức được đề cập là chậm hoặc xấu. Mã tốt là có thể đọc và duy trì. Vì vậy, vấn đề là câu hỏi không phải là câu trả lời. Chúng ta nên yêu cầu giải pháp tốt nhất cho một vấn đề không phải là giải pháp một dòng.
phobie

7
Mất z4 = {}và thay đổi dòng tiếp theo thành z4 = x.copy()- tốt hơn là chỉ mã tốt không làm những việc không cần thiết (điều này làm cho nó thậm chí còn dễ đọc và dễ bảo trì hơn).
martineau

3
Đề nghị của bạn sẽ thay đổi điều này thành câu trả lời của Matthews. Trong khi câu trả lời của anh ấy là tốt, tôi nghĩ rằng của tôi là dễ đọc hơn và duy trì tốt hơn. Dòng bổ sung sẽ chỉ xấu nếu nó sẽ tốn thời gian thực hiện.
phobie

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

new = dict_merge(old, extras)

Trong số những câu trả lời mờ ám và đáng ngờ như vậy, ví dụ sáng chói này là cách tốt nhất và duy nhất để hợp nhất các câu chuyện trong Python, được chứng thực bởi nhà độc tài cho cuộc sống của chính Guido van Rossum ! Một số người khác đề nghị một nửa trong số này, nhưng không đưa nó vào một chức năng.

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

cho:

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

39

Nếu bạn nghĩ lambdas là ác thì không đọc thêm nữa. Theo yêu cầu, bạn có thể viết giải pháp nhanh và hiệu quả bộ nhớ với một biểu thức:

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}

Như đã đề xuất ở trên, sử dụng hai dòng hoặc viết một hàm có lẽ là một cách tốt hơn để đi.


33

Hãy trăn trở. Sử dụng một sự hiểu biết :

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

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

1
Là một chức năng:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight

1
Lưu một tra cứu bằng cách lặp lại trực tiếp các cặp khóa / giá trị:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger

30

Trong python3, itemsphương thức không còn trả về một danh sách , mà là một khung nhìn , hoạt động như một tập hợp. Trong trường hợp này, bạn sẽ cần phải tham gia tập hợp kể từ khi kết hợp với +sẽ không hoạt động:

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

Đối với hành vi giống python3 trong phiên bản 2.7, viewitemsphương thức sẽ hoạt động thay cho items:

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

Dù sao thì tôi thích ký hiệu này hơn vì có vẻ tự nhiên hơn khi nghĩ về nó như là một hoạt động hợp nhất được thiết lập hơn là nối (như tiêu đề cho thấy).

Biên tập:

Thêm một vài điểm cho python 3. Đầu tiên, lưu ý rằng dict(x, **y)thủ thuật sẽ không hoạt động trong python 3 trừ khi các khóa trong ylà chuỗi.

Ngoài ra, câu trả lời Chainmap của Raymond Hettinger khá thanh lịch, vì nó có thể lấy một số lượng tùy ý làm đối số, nhưng từ các tài liệu có vẻ như nó tuần tự nhìn qua một danh sách tất cả các ký tự cho mỗi lần tra cứu:

Tra cứu tìm kiếm các ánh xạ cơ bản liên tiếp cho đến khi tìm thấy một khóa.

Điều này có thể làm bạn chậm lại nếu bạn có nhiều tra cứu trong ứng dụng của mình:

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

Vì vậy, về một thứ tự cường độ chậm hơn cho tra cứu. Tôi là một fan hâm mộ của Chainmap, nhưng có vẻ ít thực tế hơn khi có thể có nhiều tra cứu.


22

Lạm dụng dẫn đến giải pháp một biểu thức cho câu trả lời của 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}

Bạn nói rằng bạn muốn một biểu thức, vì vậy tôi đã lạm dụng lambdađể ràng buộc một tên và bộ dữ liệu để ghi đè giới hạn một biểu thức của lambda. Hãy co rúm lại.

Tất nhiên bạn cũng có thể làm điều này nếu bạn không quan tâm đến việc sao chép nó:

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

22

Giải pháp đơn giản sử dụng itertools giữ gìn trật tự (các lệnh sau có trước)

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

Và đó là cách sử dụng:

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

Mặc dù các câu trả lời là tốt cho từ điển nông này , không có phương pháp nào được định nghĩa ở đây thực sự hợp nhất từ ​​điển sâu.

Ví dụ như sau:

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

Người ta sẽ mong đợi một kết quả của một cái gì đó như thế này:

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

Thay vào đó, chúng tôi nhận được điều này:

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

Mục nhập 'một' nên có 'độ sâu_2' và 'thêm' là các mục trong từ điển của nó nếu nó thực sự là một sự hợp nhất.

Sử dụng chuỗi cũng không hoạt động:

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

Kết quả trong:

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

Sự hợp nhất sâu sắc mà RCwesick đưa ra cũng tạo ra kết quả tương tự.

Vâng, nó sẽ hoạt động để hợp nhất các từ điển mẫu, nhưng không ai trong số chúng là một cơ chế chung để hợp nhất. Tôi sẽ cập nhật điều này sau khi tôi viết một phương thức hợp nhất thực sự.


11

(Chỉ dành cho Python2.7 *; có các giải pháp đơn giản hơn cho Python3 *.)

Nếu bạn không phản đối việc nhập mô-đun thư viện chuẩn, bạn có thể làm

from functools import reduce

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

( or aBit trong lambdalà cần thiết vì dict.updateluôn luôn trả Nonevề thành công.)


11

Nếu bạn không nhớ đột biến x,

x.update(y) or x

Đơn giản, dễ đọc, biểu diễn. Bạn biết update() luôn luôn trả về None, đó là một giá trị sai. Vì vậy, biểu thức trên sẽ luôn luôn đánh giáx , sau khi cập nhật nó.

Các phương thức đột biến trong thư viện chuẩn (như .update()) trả về Nonetheo quy ước, do đó mẫu này cũng sẽ hoạt động trên các thư viện đó. Nếu bạn đang sử dụng một phương pháp không tuân theo quy ước này, thì orcó thể không hoạt động. Tuy nhiên, thay vào đó, bạn có thể sử dụng một màn hình và chỉ mục tuple để biến nó thành một biểu thức duy nhất. Điều này hoạt động bất kể yếu tố đầu tiên đánh giá là gì.

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

Nếu bạn chưa có xbiến, bạn có thể sử dụng lambdađể tạo cục bộ mà không cần sử dụng câu lệnh gán. Số tiền này được sử dụng lambdanhư một biểu thức let , là một kỹ thuật phổ biến trong các ngôn ngữ chức năng, nhưng có thể là unpythonic.

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

Mặc dù nó không khác biệt so với cách sử dụng toán tử hải mã mới sau đây (chỉ dành cho Python 3,8+):

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

Nếu bạn muốn có một bản sao, kiểu PEP 448 là dễ nhất {**x, **y}. Nhưng nếu điều đó không có sẵn trong phiên bản Python (cũ hơn) của bạn, thì hãy để mẫu cũng hoạt động ở đây.

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

(Tất nhiên, điều đó tương đương với (z := x.copy()).update(y) or z, nhưng nếu phiên bản Python của bạn đủ mới cho điều đó, thì kiểu PEP 448 sẽ có sẵn.)


10

Dựa trên các ý tưởng ở đây và các nơi khác Tôi đã hiểu một chức năng:

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

Cách sử dụng (đã thử nghiệm trong 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})

Bạn có thể sử dụng lambda thay thế.


10

Vấn đề tôi gặp phải với các giải pháp được liệt kê cho đến nay là, trong từ điển được hợp nhất, giá trị của khóa "b" là 10 nhưng, theo cách nghĩ của tôi, nó phải là 12. Trong ánh sáng đó, tôi trình bày như sau:

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

Các kết quả:

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
Bạn có thể quan tâm cytoolz.merge_with( toolz.readthedocs.io/en/latest/iêng )
bli

10

Thật ngớ ngẩn mà .updatekhông trả lại được gì.
Tôi chỉ sử dụng một hàm trợ giúp đơn giản để giải quyết vấn đề:

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

Ví dụ:

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

Điều này sẽ giải quyết vấn đề của bạn.


9

Điều này có thể được thực hiện với một sự hiểu biết chính tả duy nhất:

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

Theo quan điểm của tôi, câu trả lời tốt nhất cho phần 'biểu thức đơn' vì không cần thêm chức năng nào và nó rất ngắn.


Tôi nghi ngờ hiệu suất sẽ không được tốt mặc dù; tạo một tập hợp từ mỗi lệnh sau đó chỉ lặp qua các khóa nghĩa là tìm kiếm giá trị khác mỗi lần (mặc dù tương đối nhanh, vẫn tăng thứ tự của hàm để chia tỷ lệ)
Breezer 16/2/2017

2
tất cả phụ thuộc vào phiên bản của con trăn mà chúng ta đang sử dụng. Trong 3,5 trở lên {** x, ** y} đưa ra từ điển được nối
Rashid Mv

9

Sẽ có một tùy chọn mới khi Python 3.8 phát hành ( dự kiến ​​vào ngày 20 tháng 10 năm 2019 ), nhờ PEP 572: Biểu thức chuyển nhượng . Toán tử biểu thức gán mới :=cho phép bạn gán kết quả của copyvà vẫn sử dụng nó để gọi update, để lại mã kết hợp thành một biểu thức, thay vì hai câu lệnh, thay đổi:

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

đến:

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

trong khi hành xử giống hệt nhau theo mọi cách. Nếu bạn cũng phải trả về kết quả dict(bạn đã yêu cầu một biểu thức trả về dict; ở trên tạo và gán cho newdict, nhưng không trả về nó, vì vậy bạn không thể sử dụng nó để truyền một đối số cho một hàm như là, la myfunc((newdict := dict1.copy()).update(dict2))) , sau đó chỉ cần thêm or newdictvào cuối (vì updatetrả về None, đó là giả, sau đó nó sẽ đánh giá và trả về newdictlà kết quả của biểu thức):

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

Thông báo trước quan trọng: Nói chung, tôi không khuyến khích phương pháp này theo hướng có lợi cho:

newdict = {**dict1, **dict2}

Cách tiếp cận giải nén rõ ràng hơn (với bất kỳ ai biết về giải nén tổng quát ở nơi đầu tiên, mà bạn nên ), không yêu cầu một tên cho kết quả nào cả (vì vậy nó ngắn gọn hơn nhiều khi xây dựng tạm thời ngay lập tức được chuyển đến chức năng hoặc được bao gồm trong một list/ tuplenghĩa đen hoặc tương tự), và gần như chắc chắn cũng nhanh hơn, là (trên CPython) gần tương đương với:

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

nhưng được thực hiện ở lớp C, sử dụng dictAPI cụ thể , do đó không có chi phí tra cứu / ràng buộc phương thức động hoặc chức năng gửi công cụ gọi (trong đó(newdict := dict1.copy()).update(dict2) không thể tránh khỏi trùng lặp với hai lớp gốc trong hành vi, thực hiện công việc theo các bước riêng biệt, với tra cứu động / ràng buộc / gọi các phương thức.

Nó cũng mở rộng hơn, vì hợp nhất ba dicts là hiển nhiên:

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

trong đó việc sử dụng biểu thức gán sẽ không mở rộng như thế; gần nhất bạn có thể nhận được sẽ là:

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

hoặc không có bộ dữ liệu tạm thời của Nones, nhưng với thử nghiệm tính trung thực của từng Nonekết quả:

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

một trong hai trong số đó rõ ràng là nhiều xấu xí, và bao gồm sự thiếu hiệu quả hơn nữa (hoặc tạm thời lãng phí tuplecủa Nones để tách dấu phẩy, hoặc thử nghiệm truthiness vô nghĩa của mỗi update's Nonetrả lại cho ortách).

Lợi thế thực sự duy nhất cho cách tiếp cận biểu thức gán xảy ra nếu:

  1. Bạn có mã chung cần xử lý cả sets và dicts (cả hai đều hỗ trợ copyupdatevì vậy mã hoạt động gần như bạn mong đợi)
  2. Bạn mong đợi nhận được các đối tượng giống như độc tài , không chỉ dictriêng nó, và phải bảo tồn loại và ngữ nghĩa của phía bên trái (thay vì kết thúc bằng một đồng bằng dict). Mặc dù myspecialdict({**speciala, **specialb})có thể hoạt động, nhưng nó sẽ liên quan đến tạm thời thêm dictvà nếu myspecialdictcác tính năng đơn giản dictkhông thể bảo toàn (ví dụ: dictgiờ đây giữ trật tự dựa trên lần xuất hiện đầu tiên của khóa và giá trị dựa trên lần xuất hiện cuối cùng của khóa; bạn có thể muốn một trong đó duy trì trật tự dựa trên cuối cùngsự xuất hiện của một khóa để cập nhật một giá trị cũng di chuyển nó đến cuối), khi đó ngữ nghĩa sẽ sai. Do phiên bản biểu thức gán sử dụng các phương thức được đặt tên (có lẽ bị quá tải để hành xử phù hợp), nên nó không bao giờ tạo ra một dict(trừ khi dict1đã là một dict), giữ nguyên kiểu gốc (và ngữ nghĩa của kiểu gốc), trong khi tránh mọi thời gian.

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}

Phương pháp này ghi đè lên xbản sao của nó. Nếu xlà một đối số chức năng thì điều này sẽ không hoạt động (xem ví dụ )
bartolo-otrit
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.