70

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

35

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

Вот пример словарей, которые я использую:

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

Я хотел бы получить результат следующим образом:

z = merge(x, y)

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

При этом, если ключ k присутствует в обоих словарях, необходимо сохранить только значение y[k]. Можете подсказать, как правильно реализовать такую функцию слияния словарей?

5 ответ(ов)

18

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

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

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

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

Если вы используете Python 2, вы даже можете убрать вызовы list(). Чтобы создать z, вы можете сделать так:

>>> z = dict(x.items() + 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
>>> z
{'a': 1, 'c': 11, 'b': 10}
7

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

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

Этот код сначала создает копию словаря x, а затем обновляет ее, добавляя или перезаписывая значения из словаря y. Таким образом, z станет объединением двух словарей. Если вам нужно только объединить два словаря и создать новый, это один из удобных подходов.

1

В ответ на ваш вопрос о сравнительной производительности двух альтернативных способов объединения словарей:

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

Вы также можете записать это так:

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

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

1

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

def merge(d1, d2, merge_fn=lambda x, y: y):
    """
    Объединяет два словаря без уничтожения, комбинируя 
    значения по дублирующимся ключам в соответствии с 
    заданной опциональной функцией слияния. По умолчанию 
    поведение заменяет значения в d1 соответствующими 
    значениями в d2. (Не существует другой общеприменимой 
    стратегии слияния, но часто у вас будут однородные 
    типы в ваших словарях, поэтому указание метода слияния 
    может быть полезным.)

    Примеры:

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

Таким образом, вы можете выбрать, как именно объединять значения, передавая свою функцию в качестве параметра merge_fn.

1

Для рекурсивного обновления словаря в Python, можно использовать следующий код:

def deepupdate(original, update):
    """
    Рекурсивно обновляет словарь.
    Вложенные словари не будут перезаписаны, а также будут обновлены.
    """
    for key, value in original.items(): 
        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
    }
}

Этот код показывает, как можно обновить словарь pluto_original с помощью данных из pluto_update, при этом вложенные словари будут обновлены без перезаписи существующих ключей. Если у вас есть вопросы или нужно что-то уточнить, не стесняйтесь задавать!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь