Как объединить два словаря в одно выражение в Python?
Я хочу слить два словаря в новый словарь, но с определенным поведением при совпадении ключей.
Вот пример словарей, которые я использую:
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 ответ(ов)
В вашем случае вы можете сделать следующее:
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}
Вы можете достичь желаемого результата, используя следующий альтернативный способ:
z = x.copy()
z.update(y)
Этот код сначала создает копию словаря x
, а затем обновляет ее, добавляя или перезаписывая значения из словаря y
. Таким образом, z
станет объединением двух словарей. Если вам нужно только объединить два словаря и создать новый, это один из удобных подходов.
В ответ на ваш вопрос о сравнительной производительности двух альтернативных способов объединения словарей:
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)
как делает это Тони, но (неудивительно) разница в записи не имеет измеримого эффекта на производительность. Используйте то, что вам кажется правильным. Конечно, он совершенно прав, указывая, что версия с двумя заявлениями значительно проще для понимания.
Я хотел что-то похожее, но с возможностью указать, как значения по дублирующимся ключам будут объединяться, поэтому я немного модифицировал код (хотя и не проводил обширного тестирования). Очевидно, что это не одно выражение, но это все же один вызов функции.
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
.
Для рекурсивного обновления словаря в 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
, при этом вложенные словари будут обновлены без перезаписи существующих ключей. Если у вас есть вопросы или нужно что-то уточнить, не стесняйтесь задавать!
Итерация по словарям с использованием циклов 'for'
Создание словаря (dict) из отдельных списков ключей и значений
Преобразование списка словарей в DataFrame pandas
Почему использовать dict.get(key) вместо dict[key]?
Получить ключ по значению в словаре