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

StackOverflow https://stackoverflow.com/questions/38987

  •  09-06-2019
  •  | 
  •  

Вопрос

У меня есть два словаря Python, и я хочу написать одно выражение, которое возвращает эти два словаря, объединенные.Тот Самый update() метод был бы тем, что мне нужно, если бы он возвращал свой результат вместо изменения dict на месте.

>>> 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() это то, что я тоже ищу.)

Это было полезно?

Решение

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

Объяснение

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

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

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

>>> z
{'a': 1, 'b': 3, 'c': 4}

Новый синтаксис для этого, предложенный в БОДРОСТЬ ДУХА 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)

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

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 будет иметь приоритет над диктантами 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 сохраняется из-за произвольного порядка наборов:

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

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

z = dict(x, **y)

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

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

Dicts предназначены для получения хэшируемых ключей (напримерзамороженные наборы или кортежи), но этот метод терпит неудачу в 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.Использование оператора ** в этом месте также не является злоупотреблением механизмом, на самом деле ** был разработан именно для передачи dicts в качестве ключевых слов.

Опять же, это не работает для 3, когда ключи не являются строками.Неявный контракт вызова заключается в том, что пространства имен принимают обычные dicts, в то время как пользователи должны передавать только аргументы ключевого слова, которые являются строками.Все остальные вызываемые объекты применяли это принудительно. 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 другим", а не слияние.

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

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

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

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

{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

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

Другие советы

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

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}

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

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

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

z = dict(x, **y)

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

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

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

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

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)

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

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

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

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

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = ChainMap({}, y, x)
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

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

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

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

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

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

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

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}

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

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

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

def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

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

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

дает:

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

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

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}

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

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

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

>>> print z
{'a': 1, 'c': 11, 'b': 10}

В 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 это струны.

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

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

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

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

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

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

Конечно, вы также можете сделать это, если не хотите копировать его:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}

Простое решение с использованием 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}

Два словаря

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. -не к выравниваться-а-список-из-списков-в-питон /

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

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

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

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

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

Результаты:

<*>

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

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

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

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

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

Проблема с решениями, перечисленными на сегодняшний день, заключается в том, что в объединенном словаре значение ключа " 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}
>>> 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 = {'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)
    }

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

from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

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

(Только для Python2.7 *; для Python3 * есть более простые решения.)

Если вы не против импортировать стандартный библиотечный модуль, вы можете сделать

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(Бит или в lambda необходим, потому что dict.update всегда возвращает None в случае успеха .)

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

Используя понимание диктанта, вы можете

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}

dc = {xi:(x[xi] if xi not in list(y.keys()) 
           else y[xi]) for xi in list(x.keys())+(list(y.keys()))}

дает

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

Обратите внимание на синтаксис для if else в понимании

{ (some_key if condition else default_key):(something_if_true if condition 
          else something_if_false) for key, value in dict_.items() }

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

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

В Python это довольно просто и может быть сделано с помощью кода, показанного в конце моего поста. Учитывая это, ответ на оригинальный вопрос будет следующим:

z = MergeDict(x, y)

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

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

Если вы когда-либо чувствовали, что предпочли бы иметь реальный объединенный dict , то вызов dict (z) приведет к его появлению (но гораздо более дорогостоящему, чем другие решения Конечно, так что это просто стоит упомянуть).

Вы также можете использовать этот класс для создания своего рода словаря для копирования при записи:

a = { 'x': 3, 'y': 4 }
b = MergeDict(a)  # we merge just one dict
b['x'] = 5
print b  # will print {'x': 5, 'y': 4}
print a  # will print {'y': 4, 'x': 3}

Вот простой код MergeDict :

class MergeDict(object):
  def __init__(self, *originals):
    self.originals = ({},) + originals[::-1]  # reversed

  def __getitem__(self, key):
    for original in self.originals:
      try:
        return original[key]
      except KeyError:
        pass
    raise KeyError(key)

  def __setitem__(self, key, value):
    self.originals[0][key] = value

  def __iter__(self):
    return iter(self.keys())

  def __repr__(self):
    return '%s(%s)' % (
      self.__class__.__name__,
      ', '.join(repr(original)
          for original in reversed(self.originals)))

  def __str__(self):
    return '{%s}' % ', '.join(
        '%r: %r' % i for i in self.iteritems())

  def iteritems(self):
    found = set()
    for original in self.originals:
      for k, v in original.iteritems():
        if k not in found:
          yield k, v
          found.add(k)

  def items(self):
    return list(self.iteritems())

  def keys(self):
    return list(k for k, _ in self.iteritems())

  def values(self):
    return list(v for _, v in self.iteritems())
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top