Вопрос-Ответ

How do I merge two dictionaries in a single expression in Python?

Как мне объединить два словаря в одно выражение в 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

Как я могу объединить два словаря Python в одном выражении?

Для словарей 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, но нужно одно выражение

Если вы еще не знакомы с 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.

Словари предназначены для приема хэшируемых ключей (например, frozensets или кортежей), но этот метод дает сбой в 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. Кроме того, Гвидо не фанат. Поэтому я не могу рекомендовать этот метод для переносимого кода с прямой совместимостью или перекрестной реализацией, что на самом деле означает, что его следует полностью избегать.

2024-01-30 00:08 python dictionary