Как мне объединить два словаря в одно выражение в Python?
Я хочу объединить два словаря в новый словарь.
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)
>>> z
{'a': 1, 'b': 3, 'c': 4}
Всякий раз, когда ключ k
присутствует в обоих словарях, должно сохраняться только значение y[k]
.
Переведено автоматически
Ответ 1
Для словарей x
и y
их неглубоко объединенный словарь z
принимает значения из y
, заменяя значения из x
.
В Python 3.9.0 или новее (выпущен 17 октября 2020 г., PEP-584
, обсуждается здесь):
z = x | y
В Python версии 3.5 или выше:
z = {**x, **y}
В Python 2 (или 3.4 или ниже) напишите функцию:
def merge_two_dicts(x, y):
z = x.copy() # start with keys and values of x
z.update(y) # modifies z with keys and values of y
return z
а теперь:
z = merge_two_dicts(x, y)
Допустим, у вас есть два словаря, и вы хотите объединить их в новый словарь, не изменяя исходные словари:
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
Желаемый результат - получить новый словарь (z
) со объединенными значениями, а значения второго словаря перезаписывают значения из первого.
>>> 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
значения s, таким образом, b
будут указывать на 3
в нашем конечном результате.
Если вы еще не знакомы с Python 3.5 или вам нужно написать обратно совместимый код, и вы хотите сделать это в одном выражении, наиболее производительный и правильный подход - поместить это в функцию:
def merge_two_dicts(x, y):
"""Given two dictionaries, merge them into a new dict as a shallow copy."""
z = x.copy()
z.update(y)
return z
и тогда у вас будет одно выражение:
z = merge_two_dicts(x, y)
Вы также можете создать функцию для объединения произвольного количества словарей, от нуля до очень большого числа:
def merge_dicts(*dict_args):
"""
Given any number of dictionaries, shallow copy and merge into a new dict,
precedence goes to key-value pairs in latter dictionaries.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
Эта функция будет работать в Python 2 и 3 для всех словарей. например, данные словари a
для g
:
z = merge_dicts(a, b, c, d, e, f, g)
и пары ключ-значение в g
будут иметь приоритет над словарями a
to 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())
Этот пример демонстрирует, что происходит, когда значения не могут быть хэшированы:
>>> 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
конструктор и выполняется очень быстро и экономно с точки зрения памяти (даже немного больше, чем наш двухэтапный процесс), но если вы точно не знаете, что здесь происходит (то есть, второй dict передается в качестве аргументов ключевого слова конструктору dict), его трудно читать, это не предполагаемое использование, и поэтому он не является Pythonic.
Вот пример использования, исправленного в django.
Словари предназначены для приема хэшируемых ключей (например, frozenset
s или кортежей), но этот метод дает сбой в Python 3, когда ключи не являются строками.
>>> c = dict(a, **b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
Из списка рассылки Гвидо ван Россум, создатель языка, написал:
Я согласен с объявлением dict({}, **{1:3}) незаконным, поскольку, в конце концов, это злоупотребление механизмом **.
и
Очевидно, что dict(x, ** y) известен как "классный взлом" для "вызова x.update (y) и возврата x". Лично я нахожу это скорее отвратительным, чем крутым.
Насколько я понимаю (а также насколько понимает создатель языка), предполагаемое использование для dict(**y)
предназначено для создания словарей в целях удобства чтения, например:
dict(a=1, b=10, c=11)
вместо
{'a': 1, 'b': 10, 'c': 11}
Несмотря на то, что говорит Гвидо,
dict(x, **y)
соответствует спецификации dict, которая, кстати. работает как для Python 2, так и для 3. Тот факт, что это работает только для строковых ключей, является прямым следствием того, как работают параметры ключевых слов, а не недостатком dict . Использование оператора ** в этом месте также не является злоупотреблением механизмом, на самом деле, ** был разработан именно для передачи словарей в качестве ключевых слов.
Опять же, это не работает для 3, когда ключи не являются строками. Неявный контракт вызова заключается в том, что пространства имен принимают обычные словари, в то время как пользователи должны передавать только аргументы ключевого слова, которые являются строками. Все другие вызываемые объекты применяли это. 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 другим", а не слияние.
ДА. Я должен вернуть вас к вопросу, который требует неглубокого объединения двух словарей, при этом значения первого перезаписываются значениями второго - в одном выражении.
Предполагая наличие двух словарей словарей, можно рекурсивно объединить их в одной функции, но вы должны быть осторожны, чтобы не изменять словари ни из одного источника, и самый надежный способ избежать этого - сделать копию при присвоении значений. Поскольку ключи должны быть хэшируемыми и, следовательно, обычно неизменяемыми, копировать их бессмысленно:
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: {}}}
Рассмотрение непредвиденных обстоятельств для других типов значений выходит далеко за рамки этого вопроса, поэтому я укажу вам на мой ответ на канонический вопрос о "Слиянии словарей словарей".
Эти подходы менее производительны, но они обеспечат правильное поведение. Они будут намного менее производительными, чем copy
и update
или новая распаковка, потому что они перебирают каждую пару ключ-значение на более высоком уровне абстракции, но они действительно соблюдают порядок приоритета (последние словари имеют приоритет)
Вы также можете вручную объединить словари в цепочку внутри dict comprehension:
{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7
или в Python 2.6 (и, возможно, еще в 2.4, когда были введены выражения-генераторы):
dict((k, v) for d in dicts for k, v in d.items()) # iteritems in Python 2
itertools.chain
будет выполнять цепочку итераторов по парам ключ-значение в правильном порядке:
from itertools import chain
z = dict(chain(x.items(), y.items())) # iteritems in Python 2
Я собираюсь провести анализ производительности только тех способов использования, которые, как известно, работают правильно. (Автономный, чтобы вы могли копировать и вставлять самостоятельно.)
from timeit import repeat
from itertools import chain
x = dict.fromkeys('abcdefg')
y = dict.fromkeys('efghijk')
def merge_two_dicts(x, y):
z = x.copy()
z.update(y)
return z
min(repeat(lambda: {**x, **y}))
min(repeat(lambda: merge_two_dicts(x, y)))
min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
min(repeat(lambda: dict(chain(x.items(), y.items()))))
min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))
В Python 3.8.1 NixOS:
>>> min(repeat(lambda: {**x, **y}))
1.0804965235292912
>>> min(repeat(lambda: merge_two_dicts(x, y)))
1.636518670246005
>>> min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
3.1779992282390594
>>> min(repeat(lambda: dict(chain(x.items(), y.items()))))
2.740647904574871
>>> min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))
4.266070580109954
$ uname -a
Linux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux
Ответ 2
В вашем случае вы можете сделать:
z = dict(list(x.items()) + list(y.items()))
Это, как вы хотите, поместит окончательный dict в z
, и значение для key b
будет должным образом переопределено значением второго (y
) dict:
>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}
Если вы используете Python 2, вы даже можете удалить list()
вызовы. Для создания z:
>>> z = dict(x.items() + 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
>>> z
{'a': 1, 'c': 11, 'b': 10}
Ответ 3
Альтернатива:
z = x.copy()
z.update(y)
Ответ 4
Другой, более краткий вариант:
z = dict(x, **y)
Примечание: это стало популярным ответом, но важно отметить, что если y
имеет какие-либо нестроковые ключи, тот факт, что это вообще работает, является злоупотреблением деталями реализации CPython, и это не работает в Python 3 или в PyPy, IronPython или Jython. Кроме того, Гвидо не фанат. Поэтому я не могу рекомендовать этот метод для переносимого кода с прямой совместимостью или перекрестной реализацией, что на самом деле означает, что его следует полностью избегать.