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

91
d3 = dict(d1, **d2)

Я так понимаю, это объединяет словарь. Но уникальна ли она? Что, если d1 имеет тот же ключ, что и d2, но другое значение? Я бы хотел, чтобы d1 и d2 были объединены, но d1 имеет приоритет, если есть повторяющийся ключ.

TIMEX
источник
9
Имейте в виду, что этот трюк считается злоупотреблением **передачей аргументов ключевого слова, если только все ключи не d2являются строками. Если не все ключи d2являются строками, это не удается в Python 3.2 и в альтернативных реализациях Python, таких как Jython, IronPython и PyPy. См., Например, mail.python.org/pipermail/python-dev/2010-April/099459.html .
Марк Дикинсон

Ответы:

154

Вы можете использовать .update()метод, если вам больше не нужен оригинал d2:

Обновите словарь парами ключ / значение из других, перезаписав существующие ключи . Вернуться None.

Например:

>>> d1 = {'a': 1, 'b': 2} 
>>> d2 = {'b': 1, 'c': 3}
>>> d2.update(d1)
>>> d2
{'a': 1, 'c': 3, 'b': 2}

Обновить:

Конечно, вы можете сначала скопировать словарь, чтобы создать новый объединенный. Это может быть необходимо, а может и нет. Если у вас есть составные объекты (объекты, которые содержат другие объекты, такие как списки или экземпляры классов) в вашем словаре, copy.deepcopyтакже следует учитывать.

Феликс Клинг
источник
1
В этом случае элементы d1 должны правильно получить приоритет, если будут обнаружены конфликтующие ключи
Трей Ханнер,
Если он вам все еще нужен, просто сделайте копию. d3 = d2.copy () d3.update (d1), но я бы хотел, чтобы к языку добавлялись d1 + d2.
Сентябрь
4
d1 + d2 проблематично, потому что один словарь должен иметь приоритет во время конфликтов, и не особенно очевидно, какой из них.
rjh
d1 + d2 будет реализован только в том случае, если Python получит мультиотображение, в противном случае неоднозначность для пользователя будет слишком запутанной для 8-байтового увеличения набора.
Ник Бастин,
В этом примере у вас есть объекты в словаре: isinstance(int, object) is Trueно deepcopyэто не кажется необходимым.
Энтони Хэтчкинс
43

В Python2

d1={'a':1,'b':2}
d2={'a':10,'c':3}

d1 отменяет d2:

dict(d2,**d1)
# {'a': 1, 'c': 3, 'b': 2}

d2 отменяет d1:

dict(d1,**d2)
# {'a': 10, 'c': 3, 'b': 2}

Такое поведение не просто случайность реализации; это гарантируется в документации :

Если ключ указан как в позиционном аргументе, так и в качестве аргумента ключевого слова, значение, связанное с ключевым словом, сохраняется в словаре.

Unutbu
источник
3
В ваших примерах произойдет сбой (возникнет ошибка TypeError) в Python 3.2 и в текущих версиях Jython, PyPy и IronPython: для этих версий Python при передаче словаря с **обозначением все ключи этого словаря должны быть строками. См. Ветку python-dev, начинающуюся с mail.python.org/pipermail/python-dev/2010-April/099427.html, чтобы узнать больше.
Марк Дикинсон
@Mark: Спасибо за внимание. Я отредактировал код, чтобы сделать его совместимым с реализациями, отличными от CPython.
unutbu
3
он не работает, если ваши ключи представляют собой кортежи строк и чисел. например, для d1 = {(1, 'a'): 1, (1, 'b'): 0,} d2 = {(1, 'a'): 1, (2, 'b'): 2, (2, 'a'): 1,}
MySchizoBuddy
Что касается синтаксиса распаковки, см. Этот пост , чтобы узнать об изменениях, которые появятся в python 3.5.
Иоаннис Филиппидис
Я собирался сказать, что это d = dict(**d1, **d2)работает, но @IoannisFilippidis ссылается на это в своем комментарии. Возможно, включение здесь фрагмента было бы более ясным, так что вот он.
dwanderson
14

Если вы хотите d1иметь приоритет в конфликтах, сделайте:

d3 = d2.copy()
d3.update(d1)

В противном случае поменяйте местами d2и d1.

цот
источник
1

Мое решение - определить функцию слияния . Это не сложно и стоит всего одну строчку. Вот код на Python 3.

from functools import reduce
from operator import or_

def merge(*dicts):
    return { k: reduce(lambda d, x: x.get(k, d), dicts, None) for k in reduce(or_, map(lambda x: x.keys(), dicts), set()) }

Тесты

>>> d = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> d_letters = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d, d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d_letters, d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> merge(d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge()
{}

Он работает для произвольного количества словарных аргументов. Если бы в этом словаре были какие-либо повторяющиеся ключи, победит ключ из самого правого словаря в списке аргументов.

Лэй Чжао
источник
1
Простой цикл с .updateвызовом в нем ( merged={}за которым следует for d in dict: merged.update(d)) будет короче, удобочитаемее и эффективнее.
Марк Дикинсон
1
Или, если вы действительно хотите использовать reduceи lambda, как насчет return reduce(lambda x, y: x.update(y) or x, dicts, {})?
Марк Дикинсон
1
Вы можете опробовать свой код в оболочке и убедиться, что он правильный. Я пытался написать функцию, которая может принимать различное количество аргументов словаря с той же функциональностью. Лучше не использовать x.update (y) под лямбдой, потому что он всегда возвращает None . И я пытаюсь написать более общую функцию merge_with, которая принимает различное количество аргументов словаря и обрабатывает повторяющиеся ключи с помощью предоставленной функции. Как только я закончу, я отправлю его в другую ветку, где решение будет более актуальным.
Лэй Чжао,
Вот ссылка, где я написал более общее решение. Добро пожаловать и посмотрите.
Лэй Чжао
1

Начиная с Python 3.9, оператор |создает новый словарь с объединенными ключами и значениями из двух словарей:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d3 = d2 | d1
# d3: {'b': 2, 'c': 3, 'a': 1}

Этот:

Создает новый словарь d3 с объединенными ключами и значениями d2 и d1. Значения d1 имеют приоритет, когда d2 и d1 имеют общие ключи.


Также обратите внимание на |=оператор, который изменяет d2 путем объединения d1 с приоритетом значений d1:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d2 |= d1
# d2: {'b': 2, 'c': 3, 'a': 1}

Ксавье Гихо
источник
0

Я считаю, что, как указано выше, использование d2.update(d1)- лучший подход и что вы также можете скопироватьd2 сначала если вам все еще нужно.

Хотя я хочу отметить, что dict(d1, **d2)на самом деле это плохой способ слияния словарей в целом, поскольку аргументы ключевых слов должны быть строками, поэтому он не удастся, если у вас есть dictтакие как:

{
  1: 'foo',
  2: 'bar'
}
Оливье Мелансон
источник