我有一个字典D,其中包含我的应用程序的默认设置。它有一个复杂的层次结构,例如列表,以及这些列表中的更多dicts(例如,它可能有一个模块列表,并且在每个模块中还有更多的dicts,有时会有更多的列表和更多的dicts等)。
我还有一个小的首选词典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}]}
有很多关于以不同方式合并字典,添加条目等的类似帖子,但它们似乎都没有解决这个问题。这似乎是一项非常常见的任务,但我无法弄清楚如何做到这一点,所以我很欣赏任何指针。 欢呼声,
(P.S。我也希望保持所有列表的顺序,因为它会反映在GUI中)
修改: 在我的解释中,我似乎并不是很清楚。对于那个很抱歉。上面的例子是一个非常简单的玩具示例。我的实际数据(保存到JSON时)大约是50K。层次结构非常深入,我在列表内的dicts内部列出了内容。此外,原子更新规则显然不清楚(即0到10是添加还是覆盖?)。要清楚原子更新是否会被覆盖。 P覆盖D.它只是需要进一步迭代的dicts和dicts列表。 (我希望用户首选项覆盖默认设置有助于可视化)。我在上面的玩具示例中也省略了一个重要的细节,那就是列表中的词典不应该与键名相匹配(如上例所示,即带键的词典' 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解决方案,但现在这样做。
答案 0 :(得分:0)
这是一个合并你当前的两个dicts
的黑客攻击。
我知道这不是“最pythonic”的方式,但它可以处理像你这样的dicts
并提供所需的输出。
在我的回答中,我正在使用来自itertools groupby
的{{1}}和zip_longest
。
以下是我的回答:
module
输出:
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)
答案 1 :(得分:0)
我希望有更多的pythonic解决方案(更简洁)。这是一个类似C的解决方案(这是我来自的地方)。
注意:下面的D和P是非常简化的玩具示例。实际上,他们在列表内部的列表内的列表中非常深入。这可能不会涵盖所有个案,但它似乎适用于我的数据(保存到json时约为50KB)。
输出:
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))