Объедините два словаря словарей (Python)

Есть ли простой способ объединить два словаря словарей в Python? Вот что мне нужно:

dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}}

result = dict_union(dict1, dict2)
# => result = {'A' : {'B' : 'C', 'D' : 'E'}}

Я создал функцию грубой силы, которая это делает, но я искал более компактное решение:

def dict_union(train, wagon):
    for key, val in wagon.iteritems():
        if not isinstance(val, dict):
            train[key] = val
        else:
            subdict = train.setdefault(key, {})
            dict_union(subdict, val)

person Cat    schedule 06.06.2011    source источник
comment
Я не понимаю, что вы хотите, чтобы произошло, когда структура диктов не совпадает. Например, если dict3 = {'A': 'F'}, то при использовании вашей версии здесь dict_union(dict3, dict2) выдает TypeError. Это желаемое поведение?   -  person Cosmologicon    schedule 06.06.2011


Ответы (6)


Вот класс RUDict (для Recursive-Update dict), который реализует поведение, которое вы ищете:

class RUDict(dict):

    def __init__(self, *args, **kw):
        super(RUDict,self).__init__(*args, **kw)

    def update(self, E=None, **F):
        if E is not None:
            if 'keys' in dir(E) and callable(getattr(E, 'keys')):
                for k in E:
                    if k in self:  # existing ...must recurse into both sides
                        self.r_update(k, E)
                    else: # doesn't currently exist, just update
                        self[k] = E[k]
            else:
                for (k, v) in E:
                    self.r_update(k, {k:v})

        for k in F:
            self.r_update(k, {k:F[k]})

    def r_update(self, key, other_dict):
        if isinstance(self[key], dict) and isinstance(other_dict[key], dict):
            od = RUDict(self[key])
            nd = other_dict[key]
            od.update(nd)
            self[key] = od
        else:
            self[key] = other_dict[key]


def test():
    dict1 = {'A' : {'B' : 'C'}}
    dict2 = {'A' : {'D' : 'E'}}

    dx = RUDict(dict1)
    dx.update(dict2)
    print(dx)


if __name__ == '__main__':
    test()


>>> import RUDict
>>> RUDict.test()
{'A': {'B': 'C', 'D': 'E'}}
>>>
person Gerrat    schedule 09.12.2011

Это решение довольно компактно. Это уродливо, но вы просите довольно сложное поведение:

dict_union = lambda d1,d2: dict((x,(dict_union(d1.get(x,{}),d2[x]) if
  isinstance(d2.get(x),dict) else d2.get(x,d1.get(x)))) for x in
  set(d1.keys()+d2.keys()))
person Cosmologicon    schedule 06.06.2011

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

def dictCompressor(*args):
    output = {x:{} for mydict in args for x,_ in mydict.items()}
    for mydict in args:
        for x,y in mydict.items():
            output[x].update(y)
    return output
person mroduin44    schedule 24.08.2018

Вы можете создать подкласс dict и обернуть исходный метод dict.update() версией, которая будет вызывать update() для субдиктов, а не напрямую перезаписывать субдикты. Однако это может потребовать не меньше усилий, чем ваше существующее решение.

person JAB    schedule 06.06.2011
comment
Да, но вам нужно убедиться, что любые словари в обновляемых словарях также являются экземплярами вашего подкласса. - person Cosmologicon; 06.06.2011

Должен быть рекурсивным, так как словари могут вкладываться друг в друга. Вот мой первый взгляд на это, вы, вероятно, захотите определить свое поведение, когда словари вложены на разной глубине.

def join(A, B):
    if not isinstance(A, dict) or not isinstance(B, dict):
        return A or B
    return dict([(a, join(A.get(a), B.get(a))) for a in set(A.keys()) | set(B.keys())])

def main():
    A = {'A': {'B': 'C'}, 'D': {'X': 'Y'}}
    B = {'A': {'D': 'E'}}
    print join(A, B)
person Matei Gruber    schedule 06.06.2011
comment
Это возвращает результат, отличный от функции OP для аргументов {'A': {'B': 'C'}} и {'A': 'F'}. Однако я не уверен, что ОП продумал этот пример. - person Cosmologicon; 06.06.2011
comment
Да, вы правы, это то, что я сказал о поведении, когда слова имеют разную глубину. Вы должны определить свои собственные. Я просто возвращаю первый, который не является None A or B. Вы можете сделать B or A, что и делает его код, или любое другое разрешение конфликта. - person Matei Gruber; 06.06.2011

Что касается меня, то информации недостаточно, но в любом случае, пожалуйста, найдите мой пример кода ниже:

dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}, 'B':{'C':'D'}}
output = {}
for key in (set(dict1) | set(dict2):
    output[key] = {}
    (key in dict1 and output[key].update(dict1.get(key)))
    (key in dict2 and output[key].update(dict2.get(key)))
person Artsiom Rudzenka    schedule 06.06.2011