繁体   English   中英

将一个字典合并到另一个字典中,覆盖值,包括更新列表和子字典(不覆盖列表本身)

[英]merge a dict into another, overwriting values including updating lists and sub-dicts (not overwriting the list itself)

我有一个词典D,其中包含我的应用程序的默认设置。 它具有复杂的层次结构(例如列表),并且这些列表中有更多字典(例如,它可能具有模块列表,并且在每个模块中还有其他字典,有时具有更多列表和更多字典等)。

我还有一个小的首选项字典P,其中包含该字典的任意子集(我100%确信这是一个完美的子集)。

我想将此子集P合并到默认字典D上。

我以为D.update(P)可以工作,但这会覆盖列表。 例如

D={'i':0, 'j':1, 'modules':[{'a':1}, {'b':2}, {'c':3}] }
P={'i':10, 'modules':[{'c':30}] }

D.update()

# gives {'i': 10, 'j': 1, 'modules': [{'c': 30}]}
# I'd like {'i': 10, 'j': 1, 'modules': [{'a': 1}, {'b': 2}, {'c': 30}]}

关于以不同方式合并字典,添加条目等,有很多类似的文章,但是似乎都没有一个确切的问题。 这似乎是一个非常普通的任务,但是我不知道该怎么做,所以我很感激任何指针。 干杯,

(PS我也想维护所有列表的顺序,因为它会反映在GUI中)

编辑 :看来我的解释不太清楚。 对于那个很抱歉。 上面的示例是一个非常简单的玩具示例。 我的实际数据(保存为JSON时)约为50K。 层次结构非常深入,我在列表中的字典内部都有字典。在列表中的字典内部还有字典。原子更新规则也不清楚(例如,0到10是加法还是覆盖?)。 需要明确的是,原子更新正在被覆盖。 P覆盖D。仅是字典和字典列表需要进一步迭代。 (我希望用户的“首选项”覆盖“默认设置”将有助于可视化)。 我在上面的玩具示例中也省略了一个重要的细节,那就是列表中的字典不应该与键名匹配(就像上面的示例一样,即键“ a”的字典对P和D是通用的) ),但按特定键上的值。 请参阅下面的新玩具示例。

D={'i':'Hello', 'j':'World', 'modules':[{'name':'a', 'val':1}, {'name':'b', 'val':2}, {'name':'c', 'val':3}, {'name':'d', 'val':4}] }
P={'i':'Goodbye', 'modules':[{'name':'a', 'val':10}, {'name':'c', 'val':30}] }

EDIT2 :我添加了一个似乎有效的解决方案。 我希望有一个更简洁的pythonic解决方案,但是现在可以完成工作。

这是将您当前的两个dicts合并dicts的技巧。

我知道,是不是“最Python化”的方式来做到这一点,但它可以处理一个dicts喜欢你,给所需的输出。

在我的回答中,我正在使用itertools module groupbyzip_longest

这是我的答案:

from itertools import groupby, zip_longest

D = {'i':0, 'j':1, 'modules':[{'a':1}, {'b':2}, {'c':3}] }
P = {'i':10, 'modules':[{'c':30}] }

sub = list(D.items()) + list(P.items())

final = {}
for k,v in groupby(sorted(sub, key=lambda x: x[0]), lambda x: x[0]):
    bb = list(v)

    if not isinstance(bb[0][1], list):
        for j in bb:
            final[k] = max(bb, key=lambda x: x[1])[1]
    else:
        kk, ff = [], []
        for k_ in zip_longest(*[k[1] for k in bb]):
            kk += [j for j in k_ if j != None] 

        for j,m in groupby(sorted(kk, key= lambda x: list(x.keys())[0]), lambda x: list(x.keys())[0]):
            ff += ff += [dict(max([list(k.items()) for k in list(m)], key=lambda x:x))]

        final[k] = ff

print(final)

输出:

{'i': 10, 'j': 1, 'modules': [{'a': 1}, {'b': 2}, {'c': 30}]}

我希望有一个更Python化的解决方案(更加简洁)。 这是一个类似C的解决方案(更多的是我来自哪里)。

注意:下面的D和P是非常简化的玩具示例。 实际上,他们对列表内的字典内的字典很了解。 这可能无法涵盖所有情况,但似乎可以处理我的数据(保存到json时约为50KBish)。

输出:

In [2]: P
Out[2]: 
{'i': 'Goodbye',
 'modules': [{'name': 'a', 'val': 10}, {'name': 'c', 'val': 30}]}

In [3]: D
Out[3]: 
{'i': 'Hello',
 'j': 'World',
 'modules': [{'name': 'a', 'val': 1},
  {'name': 'b', 'val': 2},
  {'name': 'c', 'val': 3},
  {'name': 'd', 'val': 4}]}

In [4]: merge_dicts_by_name(P, D)
 merge_dicts_by_name  <type 'dict'> <type 'dict'>
 key: .i : Hello overwritten by Goodbye
 key: .modules : 
    merge_dicts_by_name .modules <type 'list'> <type 'list'>
    list item: .modules[0]
       merge_dicts_by_name .modules[0] <type 'dict'> <type 'dict'>
       key: .modules[0].name : a overwritten by a
       key: .modules[0].val : 1 overwritten by 10
    list item: .modules[1]
       merge_dicts_by_name .modules[1] <type 'dict'> <type 'dict'>
       key: .modules[1].name : c overwritten by c
       key: .modules[1].val : 3 overwritten by 30

In [5]: D
Out[5]: 
{'i': 'Goodbye',
 'j': 'World',
 'modules': [{'name': 'a', 'val': 10},
  {'name': 'b', 'val': 2},
  {'name': 'c', 'val': 30},
  {'name': 'd', 'val': 4}]}

码:

def merge_dicts_by_name(P, D, id_key='name', root='', depth=0, verbose=True, indent='   '):
    '''
    merge from dict (or list of dicts) P into D. 
    i.e. can think of D as Default settings, and P as a subset containing user Preferences.
    Any value in P or D can be a dict or a list of dicts
    in which case same behaviour will apply (through recursion):
        lists are iterated and dicts are matched between P and D
        dicts are matched via an id_key (only at same hierarchy depth / level)
        matching dicts are updated with same behaviour
        for anything else P overwrites D

    P : dict or list of dicts (e.g. containing user Preferences, subset of D)
    D : dict or list of dicts (e.g. Default settings)
    id_key : the key by which sub-dicts are compared against (e.g. 'name')
    root : for keeping track of full path during recursion
    depth : keep track of recursion depth (for indenting)
    verbose : dump progress to console
    indent : with what to indent (if verbose)
    '''

    if verbose:
        indent_full = indent * depth
        print(indent_full, 'merge_dicts_by_name', root, type(P), type(D))

    if type(P)==list: # D and P are lists of dicts
        assert(type(D)==type(P))
        for p_i, p_dict in enumerate(P): # iterate dicts in P
            path = root + '[' + str(p_i) + ']'
            if verbose: print(indent_full, 'list item:', path)
            d_id = p_dict[id_key] # get name of current dict
            # find corresponding dict in D 
            d_dict = D[ next(i for (i,d) in enumerate(D) if d[id_key] == d_id) ]
            merge_dicts_by_name(p_dict, d_dict, id_key=id_key, root=path, depth=depth+1, verbose=verbose, indent=indent)

    elif type(P)==dict:
        assert(type(D)==type(P))
        for k in P:
            path = root + '.' + k
            if verbose: print(indent_full, 'key:', path, end=' : ')
            if k in D:
                if type(P[k]) in [dict, list]:
                    print()
                    merge_dicts_by_name(P[k], D[k], id_key=id_key, root=path, depth=depth+1, verbose=verbose, indent=indent)
                else:
                    if verbose: print(D[k], 'overwritten by', P[k])
                    D[k] = P[k]

            else:
                print(indent_full, 'Warning: Key {} in P not found in D'.format(path))

    else:
        print(indent_full, "Warning: Don't know what to do with these types", type(P), type(D))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM