Python: объединение словарей путем обновления, но без перезаписи, если значение существует

Если у меня есть 2 изречения:

d1 = {('unit1','test1'):2,('unit1','test2'):4}
d2 = {('unit1','test1'):2,('unit1','test2'):''}

Чтобы их «слить»:

z = dict(d1.items() + d2.items())
z = {('unit1','test1'):2,('unit1','test2'):''}

Работает отлично. Кроме того, что делать, если я хочу сравнить каждое значение двух словарей и обновить d2 до d1 только в том случае, если значения в d1 пусты / None / ''?

[EDIT] Вопрос: при обновлении d2 до d1, когда такой же ключ существует, я хотел бы сохранить только числовое значение (либо из d1, либо из d2) вместо пустого значения. Если оба значения пусты, тогда нет проблем с сохранением пустого значения. Если оба имеют значения, значение d1 должно остаться. :) (много если-еще .. А пока попробую)

i.e.

d1 = {('unit1','test1'):2,('unit1','test2'):8,('unit1','test3'):''}
d2 = {('unit1','test1'):2,('unit1','test2'):'',('unit1','test3'):''}

#compare & update codes

z = {('unit1','test1'):2,('unit1','test2'):8, ('unit1','test2'):''} # 8 not overwritten by empty.

пожалуйста, помогите предложить.

Спасибо.


person siva    schedule 15.06.2011    source источник
comment
Вы уже пробовали in?   -  person Ignacio Vazquez-Abrams    schedule 15.06.2011
comment
См. также (на основе php) stackoverflow.com/questions/793464/   -  person dreftymac    schedule 02.01.2017
comment
См. также (на основе рубина) stackoverflow.com/questions/1980794/   -  person dreftymac    schedule 02.01.2017
comment
См. также: (itemgetter) stackoverflow.com/a/12118794/42223   -  person dreftymac    schedule 12.02.2019


Ответы (8)


Просто поменяйте порядок:

z = dict(d2.items() + d1.items())

Кстати, вас также может заинтересовать потенциально более быстрый метод update .

В Python 3 вы должны сначала преобразовать объекты представления в списки:

z = dict(list(d2.items()) + list(d1.items())) 

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

def mergeDictsOverwriteEmpty(d1, d2):
    res = d2.copy()
    for k,v in d2.items():
        if k not in d1 or d1[k] == '':
            res[k] = v
    return res
person phihag    schedule 15.06.2011
comment
я думаю, в этом случае .. если d1 имеет пустое значение элемента, оно перезапишет d2 значение элемента, имеющее числовое значение? - person siva; 15.06.2011
comment
@siva Обновлено с учетом вашего особого случая. - person phihag; 15.06.2011
comment
Думаю, что должно быть res=d1.copy(), иначе не будет передачи информации между диктами. - person Richard; 19.11.2014
comment
Python 3.4.3, по крайней мере, не поддерживает + между наборами элементов словаря, но вы можете достичь тех же результатов, приведя к list: dict(list(d2.items()) + list(d1.items())) - person JellicleCat; 13.07.2017
comment
itertools.chain() также может помочь. - person Frozen Flame; 02.11.2018
comment
В Python 3 вы можете просто z = {**d2, **d1}. - person Brian McCutchon; 24.04.2020

Python 2.7. Обновляет d2 парами ключ / значение d1, но только если значение d1 не равно None '' (False):

>>> d1 = dict(a=1,b=None,c=2)
>>> d2 = dict(a=None,b=2,c=1)
>>> d2.update({k:v for k,v in d1.iteritems() if v})
>>> d2
{'a': 1, 'c': 2, 'b': 2}
person Mark Tolonen    schedule 15.06.2011
comment
Это именно то, что я искал. Спасибо! - person noisebleed; 09.07.2012
comment
... который изменит ввод d2. Почему не dr={}; dr.update(d1); dr.update((k,v) for (k,v) in d2.items() if v)? - person Pierre GM; 31.08.2012
comment
Это сработало для меня d2.update({k:v for k,v in d1.iteritems() if v is not None}) - person Mauricio; 21.02.2019

Чтобы добавить в d2 ключи / значения из d1, которых нет в d2, без перезаписи существующих ключей / значений в d2:

temp = d2.copy()
d2.update(d1)
d2.update(temp)
person Ron Kalian    schedule 16.05.2018

d2.update(d1) вместо dict(d2.items() + d1.items())

person warvariuc    schedule 15.06.2011
comment
... изменит содержимое d2, что может быть не тем, что хочет OP. По крайней мере, dict(d1.items()+d2.items()) сохраняет входные данные неизменными. - person Pierre GM; 31.08.2012

Вот решение на месте (оно изменяет d2):

# assumptions: d2 is a temporary dict that can be discarded
# d1 is a dict that must be modified in place
# the modification is adding keys from d2 into d1 that do not exist in d1.

def update_non_existing_inplace(original_dict, to_add):
    to_add.update(original_dict) # to_add now holds the "final result" (O(n))
    original_dict.clear() # erase original_dict in-place (O(1))
    original_dict.update(to_add) # original_dict now holds the "final result" (O(n))
    return

Вот еще одно решение на месте, которое менее элегантно, но потенциально более эффективно, а также оставление d2 без изменений:

# assumptions: d2 is can not be modified
# d1 is a dict that must be modified in place
# the modification is adding keys from d2 into d1 that do not exist in d1.

def update_non_existing_inplace(original_dict, to_add):
    for key in to_add.iterkeys():
        if key not in original_dict:
            original_dict[key] = to_add[key]
person aong152    schedule 15.03.2017

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

dict((k,v if k in d2 and d2[k] in [None, ''] else d2[k]) for k,v in d1.iteritems())
person Artsiom Rudzenka    schedule 15.06.2011
comment
к сожалению, у меня словари не какого-то размера и ключей, а только некоторое вхождение тех же ключей со значениями diff. - person siva; 15.06.2011
comment
@siva: я изменил код, чтобы проверить d2 на ключе от d1, если это ваш случай. - person Artsiom Rudzenka; 15.06.2011

Объединение только ненулевых значений

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

d1 = {'a':1, 'b':1, 'c': '', 'd': ''}
d2 = {'a':2, 'c':2, 'd': ''}
merged_non_zero = {
    k: (d1.get(k) or d2.get(k))
    for k in set(d1) | set(d2)
}
print(merged_non_zero)

выходы:

{'a': 1, 'b': 1, 'c': 2, 'd': ''}
  • a - ›предпочитаю первое значение из d1, поскольку 'a' существует как на d1, так и на d2
  • b - ›существует только на d1
  • c - ›ненулевое значение на d2
  • d - ›пустая строка на обоих

Объяснение

Приведенный выше код создаст словарь, используя понимание dict.

если d1 имеет значение и ненулевое значение (т. е. bool(val) is True), он будет использовать значение d1[k], в противном случае - d2[k].

обратите внимание, что мы также объединяем все ключи двух dicts, поскольку они могут не иметь одинаковых ключей, используя set union - set(d1) | set(d2).

Python 3.5+ Literal Dict

если вы не используете устаревшую версию python, вам лучше использовать это.

Pythonic и более быстрый способ распаковки dict:

d1 = {'a':1, 'b':1}
d2 = {'a':2, 'c':2}
merged = {**d1, **d2}  # priority from right to left
print(merged)

{'a': 2, 'b': 1, 'c': 2}

это проще и быстрее, чем альтернатива dict(list(d2.items()) + list(d1.items())):

d1 = {i: 1 for i in range(1000000)}
d2 = {i: 2 for i in range(2000000)}

%timeit dict(list(d1.items()) + list(d2.items())) 
402 ms ± 33.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit {**d1, **d2}
144 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

подробнее об этом см. PEP448:

Ключи в словаре остаются в порядке приоритета справа налево, поэтому {** {'a': 1}, 'a': 2, ** {'a': 3}} оценивается как {'a' : 3}. Нет ограничений по количеству или положению распаковок.

person ShmulikA    schedule 15.05.2020
comment
Этот ответ будет совершенно неверным, если вы не сделаете это как {**d2, **d1}. Вам все равно нужно перевернуть словари, если вы не хотите, чтобы d2 перезаписывал значения в d1. - person drhagen; 12.10.2020
comment
спасибо @drhagen, я обновил его, чтобы включить лучший ответ вместе с моим предложением о том, как объединить - person ShmulikA; 13.10.2020

Если вы хотите игнорировать пустые места, например, слияние:

a = {"a": 1, "b": 2, "c": ""}
b = {"a": "", "b": 4, "c": 5}
c = {"a": "aaa", "b": ""}
d = {"a": "", "w": ""}

приводит к: {'a': 'aaa', 'b': 4, 'c': 5, 'w': ''}

Вы можете использовать эти 2 функции:

def merge_two_dicts(a, b, path=None):
    "merges b into a"
    if path is None:
        path = []
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                merge_two_dicts(a[key], b[key], path + [str(key)])
            elif a[key] == b[key]:
                pass  # same leaf value
            else:
                if a[key] and not b[key]:
                    a[key] = a[key]
                else:
                    a[key] = b[key]
        else:
            a[key] = b[key]
    return a


def merge_multiple_dicts(*a):
    output = a[0]
    if len(a) >= 2:
        for n in range(len(a) - 1):
            output = merge_two_dicts(output, a[n + 1])

    return output

Так что вы можете просто использовать merge_multiple_dicts(a,b,c,d)

person est.tenorio    schedule 05.02.2021