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

4790

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

Карл Мейер
источник
1
На случай, если вы используете альфа-версию Python 3.9, просто используйтеz = x | y
The Daleks

Ответы:

5710

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

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

  • В Python 3.5 или выше:

    z = {**x, **y}
  • В Python 2 (или 3.4 или ниже) напишите функцию:

    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

    и сейчас:

    z = merge_two_dicts(x, y)
  • В Python 3.9.0a4 или более (окончательная дата выпуска примерно октябрь 2020): PEP-584 , обсуждается здесь , был реализован для дальнейшего упрощения этого:

    z = x | y          # NOTE: 3.9+ ONLY

объяснение

Скажем, у вас есть два диктата, и вы хотите объединить их в новый, не изменяя исходные:

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

Желаемый результат - получить новый словарь ( z) со слитыми значениями, а значения второго dict перезаписывают значения из первого.

>>> 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значения, поэтому 'b'будут указывать на3 наш конечный результат.

Еще не на Python 3.5, но хочу одно выражение

Если вы еще не используете Python 3.5 или вам нужно написать обратно совместимый код, и вы хотите, чтобы это было в одном выражении , самый эффективный и правильный подход - поместить его в функцию:

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

и тогда у вас есть одно выражение:

z = merge_two_dicts(x, y)

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

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

Эта функция будет работать в Python 2 и 3 для всех диктов. например , данные dicts aв g:

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

и ключевые пары значений в gбудет иметь приоритет над dicts 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'

Вот пример, где у должен иметь приоритет, но вместо этого значение из x сохраняется из-за произвольного порядка множеств:

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

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

z = dict(x, **y)

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

Вот пример использования исправления в django .

Dicts предназначены для получения хешируемых ключей (например, 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}похоже не обрабатывает вложенные словари. содержимое вложенных ключей просто перезаписывается, а не сливается [...]. В итоге я сгорел от этих ответов, которые не сливаются рекурсивно, и я был удивлен, что никто не упомянул об этом. В моей интерпретации слова «слияние» эти ответы описывают «обновление одного диктанта другим», а не слияние.

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

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

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: {}}}

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

Менее производительный, но правильный Ad-hocs

Эти подходы менее эффективны, но они обеспечат правильное поведение. Они будут намного менее производительными, чем copyи updateили новая распаковка, потому что они перебирают каждую пару ключ-значение на более высоком уровне абстракции, но они делают соблюдать порядок старшинства (последние dicts имеют преимущество)

Вы также можете связать слова вручную в их понимании:

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

itertools.chain объединит итераторы в пары ключ-значение в правильном порядке:

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

Анализ производительности

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

import timeit

Следующее сделано в Ubuntu 14.04

В Python 2.7 (система 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

В Python 3.5 (deadsnakes PPA):

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

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

Аарон Холл
источник
9
@MohammadAzim «только строки» применяется только к раскрытию аргумента ключевого слова в вызываемых объектах, а не к обобщенному синтаксису распаковки. Чтобы продемонстрировать, что это работает: {**{(0, 1):2}}->{(0, 1): 2}
Аарон Холл
37
короткие ответы, такие как z = {**x, **y}действительно стимулируют меня
pcko1
1
Это может быть изменено, когда PEP-0584 принят. Будет реализован новый оператор объединения со следующим синтаксисом:x | y
Callam Delaney
2
Когда для ответа требуется краткая сводка, он слишком длинный.
Гринго Суаве
2
Привет, вершина - это резюме, да. Вам решать. Все это будет отличный пост в блоге. Примечание Py 3.4 и ниже EOL, 3.5 приближается к EOL в 2020-09.
Гринго Суаве
1618

В вашем случае вы можете сделать следующее:

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

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

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

Если вы используете Python 3, это будет немного сложнее. Для создания z:

>>> z = dict(list(x.items()) + list(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)

Output: {'a': 1, 'c': 11, 'b': 10}
Томас Вандер Стичеле
источник
3
Не используйте это, поскольку это очень неэффективно. (См. Результаты ниже.) Возможно, в дни Py2 это было необходимо, если функция-обертка не была опцией, но эти дни уже прошли.
Гринго Суаве
632

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

z = x.copy()
z.update(y)
Мэтью Шинкель
источник
83
Чтобы выяснить, почему это не соответствует критерию, указанному в вопросе: это не одно выражение, и оно не возвращает z.
Алекс
2
@neuronet каждый oneliner, как правило, просто перемещает код, который должен попасть в другой компонент, и решает его там. это определенно один из случаев. но другие языки имеют более приятные конструкции, чем python для этого. и иметь ссылочно прозрачный вариант, который возвращает его элемент, приятно иметь вещь.
Алекс
12
Говоря так: если вам нужно поместить две строки комментариев, объясняющих вашу одну строку кода, людям, которым вы передаете свой код ... Вы действительно сделали это одной строкой? :) Я полностью согласен, что Python не годится для этого: должен быть намного более простой способ. Хотя этот ответ более питоничен, действительно ли он все такой явный или ясный? Updateэто не одна из «основных» функций, которые люди часто используют.
Эриком
Что ж, если люди настаивают на том, чтобы сделать это простым, вы всегда можете сделать это (lambda z: z.update(y) or z)(x.copy()): P
towr
341

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

z = dict(x, **y)

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

Карл Мейер
источник
Прекрасно работает в Python 3 и PyPy и PyPy 3 , не может общаться с Jython или Iron. Учитывая, что этот шаблон явно задокументирован (см. Третью форму конструктора в этой документации), я бы сказал, что это не «деталь реализации», а преднамеренное использование функций.
amcgregor
5
@amcgregor Вы пропустили ключевую фразу «если у y есть нестроковые ключи». Это то, что не работает в Python3; тот факт, что он работает в CPython 2, является деталью реализации, на которую нельзя положиться. Если все ваши ключи гарантированно являются строками, это полностью поддерживаемая опция.
Карл Мейер
214

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

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

Тони Мейер
источник
150

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

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)

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

Зафод
источник
5
Это не работает в Python 3; items()не поддается лечению и iteritemsне существует.
Антти Хаапала
127

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

>>> 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 = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
Раймонд Хеттингер
источник
3
Но следует соблюдать осторожность при использовании ChainMap, но есть ловушка: если у вас есть дубликаты ключей, используются значения из первого сопоставления, а когда вы вызываете delon, скажем, ChainMap c удалит первое сопоставление этого ключа.
Убийца
7
@Prerit Что еще можно ожидать от этого? Это нормальный способ работы цепочек имен. Посмотрите, как работает $ PATH в bash. Удаление исполняемого файла по пути не препятствует другому исполняемому файлу с тем же именем в последующем.
Рэймонд Хеттингер
2
@Raymond Hettinger Я согласен, только что добавил осторожность. Большинство людей могут не знать об этом. : D
Убийца
@Prerit Вы могли бы использовать, dictчтобы избежать этого, то есть:dict(ChainMap({}, y, x))
wjandrea
113

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

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
rcreswick
источник
88

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

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

Стан
источник
1
Это не отвечает на вопрос. Вопрос явно требует нового словаря z из исходных словарей x и y, где значения y заменяют значения x, а не обновленный словарь. Этот ответ изменяет y на месте, добавляя значения из x. Хуже того, он не копирует эти значения, поэтому можно дополнительно изменить измененный словарь y, и изменения могут быть отражены в словаре x. @ Jérôme Я надеюсь, что этот код не вызывает ошибок в вашем приложении - по крайней мере, подумайте об использовании deepcopy для копирования значений.
Аарон Холл
1
@AaronHall согласился, что это не отвечает на вопрос. Но это отвечает моей потребности. Я понимаю эти ограничения, но в моем случае это не проблема. Думая об этом, может быть, имя вводит в заблуждение, так как это может вызвать глубокую копию, которую он не предоставляет. Но это касается глубокого вложения. Вот еще одна реализация из Martellibot: stackoverflow.com/questions/3232943/… .
Жером
72

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

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, но я не верю, что это трудно понять.

driax
источник
71

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'}
Билал Сайед Хуссейн
источник
Чем это решение лучше, чем решение dict(x, **y)? Как вы (@CarlMeyer) упомянули в примечании к своему собственному ответу ( stackoverflow.com/a/39858/2798610 ), Гвидо считает это решение незаконным .
Blackeagle52
14
Гвидо не любит dict(x, **y)по (очень хорошей) причине, что он полагается yтолько на наличие ключей, которые являются допустимыми именами аргументов ключевых слов (если вы не используете CPython 2.7, где конструктор dict обманывает). Это возражение / ограничение не относится к PEP 448, который обобщает **синтаксис распаковки для наложения литералов. Таким образом, это решение имеет тот же краткий характер, что dict(x, **y)и без недостатков.
Карл Мейер
62
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

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

Грег Хьюгилл
источник
В Python 3 вы получите TypeError: неподдерживаемый тип (ы) операндов для +: 'dict_items' и 'dict_items' ... вы должны инкапсулировать каждый dict с помощью list (), например: dict (list (x.items ()) + list (y.items ()))
JustSaid
49

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

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

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

phobie
источник
3
но это три утверждения, а не одно выражение
Фортран
14
Да! Упомянутые решения с одним выражением являются либо медленными, либо злыми. Хороший код удобен для чтения и сопровождения. Таким образом, проблема - это вопрос, а не ответ. Мы должны просить лучшего решения проблемы, а не однострочного решения.
Фоби
7
Потеряйте z4 = {}и измените следующую строку на z4 = x.copy()- лучше, чем просто хороший код не делает ненужных вещей (что делает его еще более читаемым и обслуживаемым).
Мартино
3
Ваше предложение изменит это на ответ Мэтьюса. Хотя его ответ хорош, я думаю, что мой лучше читается и лучше поддерживается. Дополнительная строка будет плохой, только если это будет стоить времени выполнения.
Фоби
47
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'}
Сэм Уоткинс
источник
39

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

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}

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

EMS
источник
33

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

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

>>> print z
{'a': 1, 'c': 11, 'b': 10}
Робиньо
источник
1
Как функция:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight
1
Сохраните поиск, выполнив итерацию пар ключ / значение напрямую:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger
30

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

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

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

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

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

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

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

Кроме того, ответ Рэймонда Хеттингера на Chainmap довольно элегантен, поскольку он может принимать в качестве аргументов произвольное количество диктов, но из документов он выглядит так, будто последовательно просматривает список всех диктов для каждого поиска:

Поиски последовательно выполняют поиск соответствующих отображений, пока не будет найден ключ.

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

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

beardc
источник
22

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

>>> 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}
Клаудиу
источник
22

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

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, 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}
reubano
источник
16

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

Примеры следуют:

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

Элемент 'one' должен был иметь значения 'deep_2' и 'extra' в качестве элементов внутри своего словаря, если он действительно был слиянием.

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

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

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

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

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

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

Тхань Лим
источник
11

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

KJo
источник
11

Если не возражаешь, мутируя x,

x.update(y) or x

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

Методы мутации в стандартной библиотеке (например .update()) возвращаются Noneпо соглашению, поэтому этот шаблон будет работать и с ними. Если вы используете метод, который не следует этому соглашению, он orможет не работать. Но вы можете использовать отображение и индекс кортежа, чтобы сделать его одним выражением. Это работает независимо от того, что оценивает первый элемент.

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

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

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

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

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

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

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

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

gilch
источник
10

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

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

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

Bijou Trouvaille
источник
10

Проблема, с которой я столкнулся с решениями, перечисленными на сегодняшний день, заключается в том, что в объединенном словаре значение ключа «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}
upandacross
источник
1
Вы можете быть заинтересованы в cytoolz.merge_with( toolz.readthedocs.io/en/latest/... )
BLI
10

Это так глупо, что .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
Получить бесплатно
источник
10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

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

reetesh11
источник
9

Это можно сделать с помощью единого понимания:

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

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

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

Благодаря PEP 572: Выражения назначения появится новая опция при выпуске Python 3.8 ( запланировано на 20 октября 2019 г. ) . Новый оператор выражения присваивания позволяет вам присвоить результат и по-прежнему использовать его для вызова , оставляя объединенный код одним выражением, а не двумя операторами, изменяя::=copyupdate

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}

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

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

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

Это также более расширяемый, так как объединение трех dict s очевидно:

 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которые не могут быть сохранены (например, обычныеdict s теперь сохраняют порядок, основанный на первом появлении ключа, и значение, основанное на последнем появлении ключа; вы можете захотеть тот, который сохраняет порядок на основе последнегопоявление ключа, поэтому обновление значения также перемещает его в конец), тогда семантика будет неправильной. Поскольку версия выражения присваивания использует именованные методы (которые, по-видимому, перегружены для правильного поведения), она никогда не создаетdictвообще (если dict1это уже не было dict), сохраняя исходный тип (и семантику исходного типа), избегая при этом любых временных.
ShadowRanger
источник
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}
Джон Ла Рой
источник
Этот метод перезаписывается xсвоей копией. Если xэто аргумент функции, это не сработает (см. Пример )
bartolo-otrit