Как мне объединить два словаря в одно выражение (используя объединение словарей)?

5839

У меня есть два словаря 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()я тоже ищу разрешение конфликта, который побеждает в последнем .)

0
7427

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

Для словарей xи y, zстановится неглубоко-объединенным словарь со значениями от yзамены тех из x.

  • В Python 3.9.0 или выше (выпущен 17 октября 2020 г.): PEP-584 , обсуждаемый здесь , был реализован и предоставляет простейший метод:

    z = x | y          # NOTE: 3.9+ ONLY
    
  • В 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, вы можете сделать это обратно совместимым способом. Классический питонический способ, доступный в 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 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до 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сохраняется значение from из-за произвольного порядка наборов:

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

В списке рассылки Гвидо ван Россум, создатель языка, написал:

I am fine with declaring dict({}, **{1:3}) illegal, since after all it is abuse of the ** mechanism.

а также

Apparently dict(x, **y) is going around as "cool hack" for "call x.update(y) and return x". Personally, I find it more despicable than cool.

Насколько я понимаю (а также понимание создателя языка ), предполагаемое использование dict(**y)для создания словарей для удобства чтения, например:

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

вместо того

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

Ответ на комментарии

Despite what Guido says, dict(x, **y) is in line with the dict specification, which btw. works for both Python 2 and 3. The fact that this only works for string keys is a direct consequence of how keyword parameters work and not a short-coming of dict. Nor is using the ** operator in this place an abuse of the mechanism, in fact, ** was designed precisely to pass dictionaries as keywords.

Опять же, это не работает для 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()) is still the most readable solution for Python 2. Readability counts.

Мой ответ: на merge_two_dicts(x, y)самом деле мне кажется намного яснее, если нас действительно беспокоит читабельность. И он несовместим с предыдущими версиями, поскольку Python 2 становится все более устаревшим.

{**x, **y} does not seem to handle nested dictionaries. the contents of nested keys are simply overwritten, not merged [...] I ended up being burnt by these answers that do not merge recursively and I was surprised no one mentioned it. In my interpretation of the word "merging" these answers describe "updating one dict with another", and not merging.

да. Я должен отослать вас обратно к вопросу, который требует неглубокого слияния двух словарей, при этом значения первого заменяются значениями второго - в одном выражении.

Предполагая, что два словаря словарей, можно рекурсивно объединить их в одну функцию, но вы должны быть осторожны, чтобы не изменять словари из любого источника, и самый надежный способ избежать этого - сделать копию при присвоении значений. Поскольку ключи должны быть хешируемыми и, следовательно, неизменными, их копировать бессмысленно:

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 :

{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

Ресурсы по словарям

23
  • 22
    @MohammadAzim "только строки" применяется только к раскрытию аргументов ключевого слова в вызываемых объектах, а не к обобщенному синтаксису распаковки. Чтобы продемонстрировать, что это работает: {**{(0, 1):2}}->{(0, 1): 2}Aaron Hall 16 мая '19 в 16:07
  • 14
    Это может быть изменено после принятия PEP-0584. Новый оператор объединения будет реализован со следующим синтаксисом:x | yCallam Delaney 28 фев '20 в 16:26
  • 3
    @ cal97g да, я обратился к этому в своем ответе около 10 дней назад: stackoverflow.com/posts/26853961/revisionsAaron Hall 28 фев '20 в 16:38
  • 11
    Привет, вверху - резюме, да. Вам решать. Все это было бы отличным постом в блоге. Обратите внимание, что Py 3.4 и ниже являются EOL, 3.5 приближаются к EOL в 2020-09. Gringo Suave 13 марта '20 в 2:09
  • 9
    Я согласен с желанием оставить старый путь позади, но иногда людям приходится работать в среде, где им доступны только старые технологии. Людям также необходимо обновлять код, и видение старого способа рядом с новым позволяет им уверенно заменить старый код эквивалентным новым кодом. Я открыт для предложений по реорганизации материала, но я думаю, что нам нужно сохранить старую информацию. Aaron Hall 17 мая '20 в 15:04
1707

В вашем случае вы можете:

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

Это, если вы этого хотите, поместит окончательный dict zи сделает значение для ключа bдолжным образом переопределенным значением second ( 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
print(z)
{'a': 1, 'c': 11, 'b': 10}
1
  • 8
    Не используйте это, так как это очень неэффективно. (См. Результаты timeit ниже.) Возможно, в дни Py2 это было необходимо, если функция-оболочка не подходила, но те дни уже прошли. Gringo Suave 11 марта '20 в 17:22
690

Альтернатива:

z = x.copy()
z.update(y)
4
  • 89
    Чтобы прояснить, почему это не соответствует критериям, указанным в вопросе: это не одно выражение и оно не возвращает z. Alexander Oh 21 марта '13 в 13:15
  • 16
    Скажем так: если вам нужно поместить две строки комментариев, объясняющих вашу одну строку кода людям, которым вы передаете свой код ... вы действительно сделали это одной строкой? :) Я полностью согласен с тем, что Python для этого не годится: должен быть более простой способ. Хотя этот ответ более питонический, действительно ли он все так ясен или ясен? Updateне является одной из основных функций, которые люди часто используют. eric 19 окт.
  • 6
    Что ж, если люди настаивают на том, чтобы сделать это одноразовым, вы всегда можете сделать (lambda z: z.update(y) or z)(x.copy()): Ptowr 24 фев '20 в 12:43
  • @AlexanderOh Требование одной строки должно уступить место ясности. Thorbjørn Ravn Andersen 18 июня в 15:36
387

Другой, более лаконичный вариант:

z = dict(x, **y)

Примечание : это стало популярным ответом, но важно отметить, что если yесть какие-либо нестроковые ключи, тот факт, что это вообще работает, является злоупотреблением деталями реализации CPython, и он не работает в Python 3, или в PyPy, IronPython или Jython. Кроме того, Гвидо не фанат . Поэтому я не могу рекомендовать этот метод для переносимого кода с прямой совместимостью или кросс-реализацией, что на самом деле означает, что его следует полностью избегать.

2
  • 2
    Прекрасно работает в Python 3, PyPy и PyPy 3 , не может разговаривать с Jython или Iron. Учитывая, что этот шаблон явно задокументирован (см. Третью форму конструктора в этой документации), я бы сказал, что это не «деталь реализации», а намеренное использование функции. amcgregor 12 апр '19 в 13:10
  • 11
    @amcgregor Вы пропустили ключевую фразу «если у y есть нестроковые ключи». Это то, что не работает в Python3; тот факт, что он работает в CPython 2, является деталью реализации, на которую нельзя полагаться. IFF все ваши ключи гарантированно будут строками, это полностью поддерживаемый вариант. Carl Meyer 10 мая '19 в 16:27
245

Вероятно, это не будет популярным ответом, но вы почти наверняка не захотите этого делать. Если вам нужна копия, которая является слиянием, используйте копию (или глубокую копию , в зависимости от того, что вы хотите), а затем обновите. Две строки кода гораздо более читабельны - более Pythonic - чем создание одной строки с помощью .items () + .items (). Явное лучше, чем неявное.

Кроме того, когда вы используете .items () (до Python 3.0), вы создаете новый список, содержащий элементы из dict. Если ваши словари большие, то это довольно много накладных расходов (два больших списка, которые будут выброшены, как только будет создан объединенный 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

ИМО, небольшое замедление между первыми двумя стоит того для удобочитаемости. Кроме того, аргументы ключевого слова для создания словаря были добавлены только в Python 2.3, тогда как copy () и update () будут работать в более старых версиях.

0
178

В последующем ответе вы спросили об относительной производительности этих двух альтернатив:

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, вполне могут быть заинтересованы во включении вашего кода (или варианта вашего подхода) в ядро ​​Python. Python используется dictво многих местах; оптимизация его операций - большое дело.

Вы также можете написать это как

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

как и Тони, но (что неудивительно) оказывается, что разница в обозначениях не оказывает заметного влияния на производительность. Используйте то, что вам нравится. Конечно, он абсолютно прав, указывая на то, что версию с двумя утверждениями намного легче понять.

1
  • 7
    Это не работает в Python 3; items()не связан и iteritemsне существует. Antti Haapala 16 марта '15 в 5: 502015-03-16 05:50
163

В Python 3.0 и более поздних версиях вы можете использовать, collections.ChainMapкоторый группирует несколько слов или других сопоставлений вместе для создания единого обновляемого представления:

>>> 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 = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}

Обновление для Python 3.9 и новее : вы можете использовать оператор объединения PEP 584 :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x | y
{'a': 1, 'b': 10, 'c': 11}
5
  • 3
    Но при использовании ChainMap следует проявлять осторожность, есть одна загвоздка: если у вас есть повторяющиеся ключи, используются значения из первого сопоставления, и когда вы вызываете a, delскажем, ChainMap c удалит первое сопоставление этого ключа. Slayer 14 фев '17 в 5:14
  • 9
    @Prerit Что еще вы от него ожидаете? Это нормальный способ работы связанных пространств имен. Рассмотрим, как работает $ PATH в bash. Удаление исполняемого файла по пути не исключает другой исполняемый файл с тем же именем дальше вверх по течению. Raymond Hettinger 15 фев '17 в 7:24
  • 3
    @Raymond Hettinger Я согласен, просто добавил предостережение. Большинство людей могут не знать об этом. : DSlayer 15 фев '17 в 15:15
  • @Prerit Вы можете использовать, чтобы dictизбежать этого, то есть:dict(ChainMap({}, y, x))wjandrea 15 июл.
  • Предлагаемая очередь редактирования заполнена, но кто-то поместил в ответ модификацию из @wjandrea, что неверно - это уже не файл a single, updateable view. Это изменение следует отменить. Gloweye 19 июл в 12:08
141

Я хотел что-то подобное, но с возможностью указать, как значения на повторяющихся ключах были объединены, поэтому я взломал это (но не сильно тестировал). Очевидно, что это не отдельное выражение, а единственный вызов функции.

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
1
  • Удобное решение, когда нежелательно стандартное поведение более коротких и простых решений (замена значений общих ключей вторым словарем). Для Python 3 iteritems () больше не доступен в dicts, и вместо этого можно просто использовать items (). Corentor 5 мар в 14:22
112

Рекурсивное / глубокое обновление словаря

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 за правки.

2
  • 2
    Это не отвечает на вопрос. Вопрос явно требует нового словаря, z, из исходных словарей, x и y, со значениями из y, заменяющими значения x, а не обновленным словарем. Этот ответ изменяет y на месте, добавляя значения из x. Хуже того, он не копирует эти значения, поэтому можно дополнительно изменить измененный словарь y, и изменения могут быть отражены в словаре x. @ Jérôme Я надеюсь, что этот код не вызывает ошибок в вашем приложении - по крайней мере, подумайте об использовании deepcopy для копирования значений. Aaron Hall 9 ноя '18 в 2:14
  • 2
    @AaronHall согласился, что это не ответ на вопрос. Но это отвечает моей потребности. Я понимаю эти ограничения, но в моем случае это не проблема. Думая об этом, возможно, название вводит в заблуждение, так как оно может вызвать глубокую копию, которую он не предоставляет. Но это касается глубокой вложенности. Вот еще одна реализация от Martellibot: stackoverflow.com/questions/3232943/… . Jérôme 9 ноя '18 в 13:24
89

Python 3.5 (PEP 448) допускает более приятный вариант синтаксиса:

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'}
2
  • Чем это решение лучше, чем dict(x, **y)-решение? Как вы (@CarlMeyer) упомянули в примечании к вашему собственному ответу ( stackoverflow.com/a/39858/2798610 ), Гвидо считает это решение незаконным . Blackeagle52 4 марта '15 в 11: 092015-03-04 11:09
  • 16
    Гвидо не любит dict(x, **y)по той (очень хорошей) причине, что он полагается yтолько на наличие ключей, которые являются действительными именами аргументов ключевого слова (если вы не используете CPython 2.7, где конструктор dict обманывает). Это возражение / ограничение не относится к PEP 448, который обобщает **синтаксис распаковки на dict литералы. Таким образом, это решение имеет такую ​​же лаконичность, что dict(x, **y)и без недостатков. Carl Meyer 4 марта '15 в 22:24
88

Лучшая версия, которую я мог придумать, не используя копию, была бы:

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, но я не думаю, что это сложно понять.

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

Для элементов с ключами в обоих словарях ('b') вы можете контролировать, какой из них попадает в вывод, поместив его последним.

1
  • 1
    В python 3 вы получите TypeError: неподдерживаемые типы операндов для +: 'dict_items' и 'dict_items' ... вы должны инкапсулировать каждый dict с помощью list (), например: dict (list (x.items ()) + list (y.items ()))justSaid 26 апр '19 в 8:45
63

Хотя на этот вопрос уже был дан ответ несколько раз, это простое решение проблемы еще не указано.

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

Он так же быстр, как z0 и зловещий z2, упомянутый выше, но его легко понять и изменить.

5
  • 3
    но это три утверждения, а не одно выражениеfortran 18 окт.
  • 16
    Да! Упомянутые решения с одним выражением либо медленные, либо злые. Хороший код читаем и удобен в обслуживании. Так что проблема в вопросе, а не в ответе. Мы должны искать наилучшее решение проблемы, а не однострочное решение. phobie 28 окт.
  • 10
    Потеряйте z4 = {}и измените следующую строку на z4 = x.copy()- лучше, чем просто хороший код не делает ненужных вещей (что делает его еще более читаемым и поддерживаемым). martineau 8 марта '13 в 18: 102013-03-08 15:10
  • 3
    Ваше предложение изменило бы это на ответ Мэтьюза. Хотя его ответ хорош, я думаю, что мой более читабельный и легче обслуживаемый. Дополнительная строка будет плохой, только если она будет стоить времени выполнения. phobie 6 мая '13 в 11:50
  • Я предлагаю вам поместить это в функциюCorman 14 июня '20 в 23: 162020-06-14 23:16
58
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Среди таких сомнительных и сомнительных ответов этот яркий пример - единственный хороший способ объединить диктовки в Python, одобренный пожизненным диктатором Гвидо ван Россумом ! Кто-то другой предложил половину этого, но не вложил в функцию.

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

дает:

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

Если вы думаете, что лямбды - это зло, не читайте дальше. По запросу вы можете написать быстрое и эффективное с точки зрения памяти решение с помощью одного выражения:

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}

Как было предложено выше, возможно, лучше использовать две строки или написать функцию.

46

Будьте питоником. Используйте понимание :

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

>>> print z
{'a': 1, 'c': 11, 'b': 10}
2
  • 1
    Как функция:def dictmerge(*args): return {i:d[i] for d in args for i in d}jessexknight 06 июн.
  • 3
    Сохраните поиск, перебирая пары ключ / значение напрямую:z={k: v for d in (x, y) for k, v in d.items()}ShadowRanger 5 марта '19 в 22: 332019-03-05 19:33
41

В python3 itemsметод больше не возвращает список , а скорее представление , которое действует как набор. В этом случае вам нужно взять set union, так как конкатенация с +не работает:

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

Для поведения, подобного python3, в версии 2.7 этот viewitemsметод должен работать вместо items:

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

В любом случае я предпочитаю эту нотацию, поскольку кажется более естественным думать о ней как о операции объединения множества, а не о конкатенации (как видно из названия).

Редактировать:

Еще пара моментов для python 3. Во-первых, обратите внимание, что этот dict(x, **y)трюк не будет работать в python 3, если ключи в нем не yявляются строками.

Кроме того, ответ Raymond Hettinger Chainmap довольно элегантен, поскольку он может принимать произвольное количество dicts в качестве аргументов, но из документов похоже, что он последовательно просматривает список всех dicts для каждого поиска:

Lookups search the underlying mappings successively until a key is found.

Это может замедлить вас, если в вашем приложении много запросов:

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, но выглядит менее практичным там, где может быть много запросов.

0
32

Два словаря

def union2(dict1, dict2):
    return dict(list(dict1.items()) + list(dict2.items()))

n словарей

def union(*dicts):
    return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))

sumимеет плохую производительность. См. Https://mathieularose.com/how-not-to-flatten-a-list-of-lists-in-python/

0
31

Простое решение с использованием itertools, которое сохраняет порядок (последние dicts имеют приоритет)

# py2
from itertools import chain, imap
merge = lambda *args: dict(chain.from_iterable(imap(dict.iteritems, args)))

# py3
from itertools import chain
merge = lambda *args: dict(chain.from_iterable(map(dict.items, 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}
0
29

Злоупотребление, приводящее к однозначному решению для ответа Мэтью :

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

Несмотря на то, что для этого поверхностного словаря ответы были хорошими , ни один из методов, определенных здесь, на самом деле не выполняет глубокое слияние словарей.

Ниже приведены примеры:

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» и «extra» в качестве элементов, если это действительно было слияние.

Использование цепочки также не работает:

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

Результаты в:

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

Глубокое слияние, которое дал rcwesick, также дает тот же результат.

Да, это сработает для слияния примеров словарей, но ни один из них не является универсальным механизмом для слияния. Я обновлю это позже, когда напишу метод, выполняющий настоящее слияние.

0
22

Если вы не против мутации x,

x.update(y) or x

Простой, читаемый, производительный. Вы знаете, что update() всегда возвращается None, а это ложное значение. Таким образом, приведенное выше выражение всегда будет оцениваться как xпосле его обновления.

Большинство изменяющих методов в стандартной библиотеке (например, .update()) возвращаются Noneпо соглашению, поэтому этот вид шаблона будет работать и с ними. Однако, если вы используете подкласс dict или какой-либо другой метод, который не следует этому соглашению, он orможет вернуть его левый операнд, что может быть не тем, что вам нужно. Вместо этого вы можете использовать отображение и индекс кортежа, которые работают независимо от того, что оценивает первый элемент (хотя это не так красиво):

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

Если у вас еще нет xпеременной, вы можете использовать ее lambdaдля создания локальной без использования оператора присваивания. Это равносильно использованию lambdaв качестве выражения let , что является распространенной техникой в ​​функциональных языках, но, возможно, не имеет смысла.

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

Хотя это не сильно отличается от следующего использования нового оператора моржа (только для Python 3.8+),

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

особенно если вы используете аргумент по умолчанию:

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

Если вам нужна копия, стиль PEP 584x | y - самый питонический в версии 3.9+. Если вам необходимо поддерживать более старые версии, проще всего использовать стиль PEP 448{**x, **y} для 3.5+. Но если это недоступно в вашей (даже более старой) версии Python, шаблон выражения let работает и здесь.

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

(Это, конечно, почти эквивалентно (z := x.copy()).update(y) or z, но если ваша версия Python достаточно нова для этого, тогда будет доступен стиль PEP 448.)

18

Новое в Python 3.9: используйте оператор объединения (|), чтобы объединитьdicts аналогичноsets:

>>> d = {'a': 1, 'b': 2}
>>> e = {'a': 9, 'c': 3}
>>> d | e
{'a': 9, 'b': 2, 'c': 3}

Для совпадающих ключей приоритет имеет правоdict .

Это также работает для |=изменения на dictместе:

>>> e |= d    # e = e | d
>>> e
{'a': 1, 'c': 3, 'b': 2}
17

Это так глупо, что .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
17

(Только для 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в случае успеха.)

0
16

Опираясь на идеи здесь и в других местах, я понял функцию:

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

Использование (проверено на 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})

Вместо этого вы можете использовать лямбду.

16

Проблема, с которой я столкнулся с решениями, перечисленными на сегодняшний день, заключается в том, что в объединенном словаре значение ключа «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
15
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Это должно решить вашу проблему.

1
  • Я рекомендую использовать счетчик .update()вместо +. Это связано с тем, что, если сумма приводит к значению 0 для любого из ключей, Counter удалит его. Eyong Kevin Enowanyo 7 августа в 18:23
15

Когда выйдет Python 3.8 ( запланированный на 20 октября 2019 г. ), появится новая опция благодаря PEP 572: Assignment Expressions . Новый оператор выражения присваивания :=позволяет вам назначить результат 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}

Подход к распаковке более ясен (для всех, кто знает об обобщенной распаковке в первую очередь, что и должно быть ), совсем не требует имени для результата (поэтому он намного более краток при создании временного объекта, который немедленно передается в function или включены в list/ tupleliteral или тому подобное), и почти наверняка также быстрее, будучи (на CPython) примерно эквивалентным:

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

но выполняется на уровне C с использованием конкретного dictAPI, поэтому не задействуются накладные расходы на поиск / привязку динамических методов или вызовы функций (где (newdict := dict1.copy()).update(dict2)неизбежно идентичны исходному двухстрочному интерфейсу , выполняя работу дискретными шагами, с динамическим поиском / привязка / вызов методов.

Он также более расширяемый, поскольку слияние трех dicts очевидно:

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

где использование выражений присваивания не масштабируется таким образом; самое близкое, что вы могли бы получить, было бы:

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

или без временного кортежа Nones, но с проверкой истинности каждого Noneрезультата:

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

любой из которых, очевидно , гораздо уродливее, и включает в себя дополнительные неэффективных (либо впустую временный tupleиз Noneс запятой для разделения, или бессмысленных тестирований truthiness каждого update«ы Noneвозвращения для orразделения).

Единственное реальное преимущество подхода выражения присваивания возникает, если:

  1. У вас есть общий код, который требует обработки как sets, так и dicts (оба из них поддерживают copyи update, поэтому код работает примерно так, как вы ожидаете)
  2. Вы ожидаете получения произвольных dict-подобных объектов , а не только dictсамого себя, и должны сохранить тип и семантику левой части (вместо того, чтобы в конечном итоге получить простой dict). Хотя это myspecialdict({**speciala, **specialb})может сработать, это потребует дополнительного временного использования dict, и если myspecialdictесть функции, dictкоторые нельзя сохранить (например, обычные dicts теперь сохраняют порядок на основе первого появления ключа, а значение на основе последнего появления ключа; вы можете захотеть тот, который сохраняет порядок на основе последнегопоявление ключа, поэтому обновление значения также перемещает его в конец), тогда семантика будет неправильной. Поскольку версия выражения присваивания использует именованные методы (которые предположительно перегружены для надлежащего поведения), она dictвообще никогда не создает a (кроме случаев, когда dict1a dict), сохраняя исходный тип (и семантику исходного типа), при этом избегая любых временных ограничений.
12

Это можно сделать с помощью одного понимания 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)
    }

На мой взгляд, лучший ответ для части «одного выражения», поскольку никаких дополнительных функций не требуется, и он короткий.

2
  • Я подозреваю, что производительность будет не очень хорошей; создание набора из каждого dict, а затем только итерация по ключам означает каждый раз новый поиск значения (хотя и относительно быстро, но все же увеличивает порядок функции для масштабирования)Breezer 16 фев '17 в 14:57
  • 3
    все зависит от версии питона, который мы используем. В версии 3.5 и выше {** x, ** y} дает объединенный словарьRashid Mv 23 дек.