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 x
và y
, z
trở thành một từ điển được hợp nhất nông với các giá trị y
thay 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 448 và có 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, y
sẽ đến lần thứ hai và các giá trị của nó sẽ thay thế x
các giá trị của nó, do đó 'b'
sẽ chỉ ra 3
trong 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 a
cho g
:
z = merge_dicts(a, b, c, d, e, f, g)
và các cặp giá trị chính g
sẽ đượ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 dict
tạ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ế **.
và
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 copy
và update
hay 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
z = x | y