Dict слиться в понимании dict

В Python 3.5 мы можем объединять сообщения с помощью распаковки с двойными сплатами.

>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> {**d1, **d2}
{1: 'one', 2: 'two', 3: 'three'}

Здорово. Похоже, это не обобщает динамические сценарии использования:

>>> ds = [d1, d2]
>>> {**d for d in ds}
SyntaxError: dict unpacking cannot be used in dict comprehension

Вместо этого мы должны сделать reduce(lambda x,y: {**x, **y}, ds, {}), что кажется намного уродливее. Почему "один очевидный способ сделать это" не допускается синтаксическим анализатором, когда в этом выражении нет никакой двусмысленности?

7 ответов

Решение

Это не совсем ответ на ваш вопрос, но я хотел бы рассмотреть возможность использования ChainMap быть идиоматичным и элегантным способом сделать то, что вы предлагаете (слияние словарей в линию):

>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> ds = [d1, d2]
>>> dict(ChainMap(*ds))
{1: 'one', 2: 'two', 3: 'three'}

Хотя это не особенно прозрачное решение, так как многие программисты могут не знать точно, как ChainMap работает. Обратите внимание, что (как указывает @AnttiHaapala) "используется первый найденный", поэтому, в зависимости от ваших намерений, вам может потребоваться позвонить reversed перед прохождением вашего dict с в ChainMap,

>>> d2 = {3: 'three', 2:'LOL'}
>>> dict(ChainMap(*ds))
{1: 'one', 2: 'two', 3: 'three'}

>>> dict(ChainMap(*reversed(ds)))
{1: 'one', 2: 'LOL', 3: 'three'}

Для меня очевидным способом является:

d_out = {}
for d in ds:
    d_out.update(d)

Это быстро и, вероятно, довольно эффективно. Я не знаю, что я могу говорить за разработчиков Python, но я не знаю, что ваша ожидаемая версия легче читается. Например, ваше понимание для меня больше похоже на комплексное понимание из-за отсутствия :, FWIW, я не думаю, что есть какая-либо техническая причина (например, двусмысленность синтаксического анализатора), что они не могли добавить эту форму распаковки понимания.

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

идиоматический, без ChainMap:

      >>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> {k: v for d in [d1, d2] for k, v in d.items()}
{1: 'one', 2: 'two', 3: 'three'}

Вы могли бы использовать itertools.chain или же itertools.chain.from_iterable:

import itertools

ds = [{'a': 1, 'b': 2}, {'c': 30, 'b': 40}]

merged_d = dict(itertools.chain(*(d.items() for d in ds)))
print(merged_d)  # {'a': 1, 'b': 40, 'c': 30}
final = {}
for data in [d1, d2]:
    final = {**final,**data}

Основано на этом решении, также упомянутом @ilgia-everilä, но делает его совместимым с Py2 и по-прежнему избегает промежуточных структур. Инкапсуляция его внутри функции делает его использование вполне читабельным.

      def merge_dicts(*dicts, **extra):
    """
    >>> merge_dicts(dict(a=1, b=1), dict(b=2, c=2), dict(c=3, d=3), d=4, e=4)
    {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 4}
    """
    return dict((
        (k,v)
        for d in dicts
        for k,v in d.items()
    ), **extra)

Вы можете определить эту функцию:

from collections import ChainMap
def mergeDicts(l):
    return dict(ChainMap(*reversed(list(l))))

Затем вы можете использовать его так:

>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> ds = [d1, d2]
>>> mergeDicts(ds)
{1: 'one', 2: 'two', 3: 'three'}
Другие вопросы по тегам