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


У меня есть два словаря 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}

Как я могу получить этот окончательный объединенный dict в z, а не x?

(Чтобы быть экстра-ясным, обработка конфликтов с последними победами dict.update()это то, что я ищу.)


3119


источник


Ответы:


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

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

Новый синтаксис для этого, предложенный в PEP 448 а также доступно на Python 3.5 , является

z = {**x, **y}

И это действительно одно выражение. В настоящее время он показан как реализованный в график выпуска для 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. например данные 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'

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

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

Другой взлом не следует использовать:

z = dict(x, **y)

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

Ниже приведен пример использования исправлено в джанго ,

Дикты предназначены для приема хешируемых ключей (например, фризонов или кортежей), но этот метод терпит неудачу в 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) и return x ". Лично я считаю это более презренным, чем   круто.

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

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 все больше устаревает.

Менее совершенные, но правильные рекламные объявления

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

Вы также можете связать dicts вручную внутри понимания 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())

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

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


3197



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

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}

1434



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

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

544



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

z = dict(x, **y)

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


271



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

Кроме того, когда вы используете .items () (pre Python 3.0), вы создаете новый список, содержащий элементы из dict. Если ваши словари большие, то это довольно много накладных расходов (два больших списка, которые будут выброшены, как только будет создан объединенный dict). update () может работать более эффективно, потому что он может проходить через второй элемент dict-by-item.

С точки зрения время :

>>> 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 () будут работать в более старых версиях.


166



In a follow-up answer, you asked about the relative performance of these two alternatives:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

On my machine, at least (a fairly ordinary x86_64 running Python 2.5.2), alternative z2 is not only shorter and simpler but also significantly faster. You can verify this for yourself using the timeit module that comes with Python.

Example 1: identical dictionaries mapping 20 consecutive integers to themselves:

% 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 wins by a factor of 3.5 or so. Different dictionaries seem to yield quite different results, but z2 always seems to come out ahead. (If you get inconsistent results for the same test, try passing in -r with a number larger than the default 3.)

Example 2: non-overlapping dictionaries mapping 252 short strings to integers and vice versa:

% 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 wins by about a factor of 10. That's a pretty big win in my book!

After comparing those two, I wondered if z1's poor performance could be attributed to the overhead of constructing the two item lists, which in turn led me to wonder if this variation might work better:

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

A few quick tests, e.g.

% 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

lead me to conclude that z3 is somewhat faster than z1, but not nearly as fast as z2. Definitely not worth all the extra typing.

This discussion is still missing something important, which is a performance comparison of these alternatives with the "obvious" way of merging two lists: using the update method. To try to keep things on an equal footing with the expressions, none of which modify x or y, I'm going to make a copy of x instead of modifying it in-place, as follows:

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

A typical result:

% 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

In other words, z0 and z2 seem to have essentially identical performance. Do you think this might be a coincidence? I don't....

In fact, I'd go so far as to claim that it's impossible for pure Python code to do any better than this. And if you can do significantly better in a C extension module, I imagine the Python folks might well be interested in incorporating your code (or a variation on your approach) into the Python core. Python uses dict in lots of places; optimizing its operations is a big deal.

You could also write this as

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

as Tony does, but (not surprisingly) the difference in notation turns out not to have any measurable effect on performance. Use whichever looks right to you. Of course, he's absolutely correct to point out that the two-statement version is much easier to understand.


115



I wanted something similar, but with the ability to specify how the values on duplicate keys were merged, so I hacked this out (but did not heavily test it). Obviously this is not a single expression, but it is a single function call.

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

86



In Python 3, you can use collections.ChainMap which groups multiple dicts or other mappings together to create a single, updateable view:

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

71



Recursively/deep update a dict

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

Demonstration:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Outputs:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Thanks rednaw for edits.


60



The best version I could think while not using copy would be:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

It's faster than dict(x.items() + y.items()) but not as fast as n = copy(a); n.update(b), at least on CPython. This version also works in Python 3 if you change iteritems() to items(), which is automatically done by the 2to3 tool.

Personally I like this version best because it describes fairly good what I want in a single functional syntax. The only minor problem is that it doesn't make completely obvious that values from y takes precedence over values from x, but I don't believe it's difficult to figure that out.


54



Python 3.5 (PEP 448) allows a nicer syntax option:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

Or even

final = {'a': 1, 'b': 1, **x, **y}

41



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

For items with keys in both dictionaries ('b'), you can control which one ends up in the output by putting that one last.


40