Как объединить два словаря в одном выражении? - PullRequest
3974 голосов
/ 02 сентября 2008

У меня есть два словаря 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() - это то, что я тоже ищу.)

Ответы [ 40 ]

4449 голосов
/ 11 ноября 2014

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

Новый синтаксис для этого, предложенный в 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 для всех диктов. например данные диктанты 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'

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

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

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

z = dict(x, **y)

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

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

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

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

1525 голосов
/ 02 сентября 2008

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

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

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

>>> 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}
590 голосов
/ 02 сентября 2008

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

z = x.copy()
z.update(y)
302 голосов
/ 02 сентября 2008

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

z = dict(x, **y)

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

185 голосов
/ 08 сентября 2008

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

131 голосов
/ 23 октября 2008

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

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)

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

96 голосов
/ 04 сентября 2008

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

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
90 голосов
/ 28 апреля 2013

В 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
70 голосов
/ 29 ноября 2011

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

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

60 голосов
/ 14 октября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...