字典合并到字典理解中
Dict merge in a dict comprehension
在python 3.5中,我们可以使用double-splat解包合并字典
>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> {**d1, **d2}
{1: 'one', 2: 'two', 3: 'three'}
酷。不过,它似乎并没有推广到动态用例:
>>> ds = [d1, d2]
>>> {**d for d in ds}
SyntaxError: dict unpacking cannot be used in dict comprehension
相反,我们必须做 reduce(lambda x,y: {**x, **y}, ds, {})
,这看起来更难看。为什么解析器不允许 "one obvious way to do it",而该表达式似乎没有任何歧义?
对我来说,最明显的方法是:
d_out = {}
for d in ds:
d_out.update(d)
这很快,而且性能可能还不错。我不知道我是否可以代表 python 开发人员,但我不知道您期望的版本更易于阅读。例如,由于缺少 :
,您的理解对我来说更像是集合理解。 FWIW,我不认为有任何技术原因(例如解析器歧义)他们不能添加这种形式的理解解包。
显然,these forms were proposed,但还没有足够普遍的支持来保证实施它们。
这不完全是您问题的答案,但我会考虑使用 ChainMap
作为一种惯用且优雅的方式来执行您的建议(在线合并词典):
>>> from collections import ChainMap
>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> ds = [d1, d2]
>>> dict(ChainMap(*ds))
{1: 'one', 2: 'two', 3: 'three'}
尽管这不是一个特别透明的解决方案,因为许多程序员可能并不确切知道 ChainMap
是如何工作的。请注意(正如@AnttiHaapala 指出的那样)“使用首先找到的”,因此,根据您的意图,您可能需要在将 dict
传递给 ChainMap
之前调用 reversed
。
>>> d2 = {3: 'three', 2: 'LOL'}
>>> ds = [d1, d2]
>>> dict(ChainMap(*ds))
{1: 'one', 2: 'two', 3: 'three'}
>>> dict(ChainMap(*reversed(ds)))
{1: 'one', 2: 'LOL', 3: 'three'}
你可以定义这个函数:
from collections import ChainMap
def mergeDicts(l):
return dict(ChainMap(*reversed(list(l))))
然后你可以像这样使用它:
>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> ds = [d1, d2]
>>> mergeDicts(ds)
{1: 'one', 2: 'two', 3: 'three'}
您可以使用 itertools.chain
或 itertools.chain.from_iterable
:
import itertools
ds = [{'a': 1, 'b': 2}, {'c': 30, 'b': 40}]
merged_d = dict(itertools.chain(*(d.items() for d in ds)))
print(merged_d) # {'a': 1, 'b': 40, 'c': 30}
基于 this solution 并且 @ilgia-everilä 也提到过,但使其与 Py2 兼容并仍然避免中间结构。将其封装在函数中使其使用起来非常易读。
def merge_dicts(*dicts, **extra):
"""
>>> merge_dicts(dict(a=1, b=1), dict(b=2, c=2), dict(c=3, d=3), d=4, e=4)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 4}
"""
return dict((
(k,v)
for d in dicts
for k,v in d.items()
), **extra)
在python 3.5中,我们可以使用double-splat解包合并字典
>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> {**d1, **d2}
{1: 'one', 2: 'two', 3: 'three'}
酷。不过,它似乎并没有推广到动态用例:
>>> ds = [d1, d2]
>>> {**d for d in ds}
SyntaxError: dict unpacking cannot be used in dict comprehension
相反,我们必须做 reduce(lambda x,y: {**x, **y}, ds, {})
,这看起来更难看。为什么解析器不允许 "one obvious way to do it",而该表达式似乎没有任何歧义?
对我来说,最明显的方法是:
d_out = {}
for d in ds:
d_out.update(d)
这很快,而且性能可能还不错。我不知道我是否可以代表 python 开发人员,但我不知道您期望的版本更易于阅读。例如,由于缺少 :
,您的理解对我来说更像是集合理解。 FWIW,我不认为有任何技术原因(例如解析器歧义)他们不能添加这种形式的理解解包。
显然,these forms were proposed,但还没有足够普遍的支持来保证实施它们。
这不完全是您问题的答案,但我会考虑使用 ChainMap
作为一种惯用且优雅的方式来执行您的建议(在线合并词典):
>>> from collections import ChainMap
>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> ds = [d1, d2]
>>> dict(ChainMap(*ds))
{1: 'one', 2: 'two', 3: 'three'}
尽管这不是一个特别透明的解决方案,因为许多程序员可能并不确切知道 ChainMap
是如何工作的。请注意(正如@AnttiHaapala 指出的那样)“使用首先找到的”,因此,根据您的意图,您可能需要在将 dict
传递给 ChainMap
之前调用 reversed
。
>>> d2 = {3: 'three', 2: 'LOL'}
>>> ds = [d1, d2]
>>> dict(ChainMap(*ds))
{1: 'one', 2: 'two', 3: 'three'}
>>> dict(ChainMap(*reversed(ds)))
{1: 'one', 2: 'LOL', 3: 'three'}
你可以定义这个函数:
from collections import ChainMap
def mergeDicts(l):
return dict(ChainMap(*reversed(list(l))))
然后你可以像这样使用它:
>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> ds = [d1, d2]
>>> mergeDicts(ds)
{1: 'one', 2: 'two', 3: 'three'}
您可以使用 itertools.chain
或 itertools.chain.from_iterable
:
import itertools
ds = [{'a': 1, 'b': 2}, {'c': 30, 'b': 40}]
merged_d = dict(itertools.chain(*(d.items() for d in ds)))
print(merged_d) # {'a': 1, 'b': 40, 'c': 30}
基于 this solution 并且 @ilgia-everilä 也提到过,但使其与 Py2 兼容并仍然避免中间结构。将其封装在函数中使其使用起来非常易读。
def merge_dicts(*dicts, **extra):
"""
>>> merge_dicts(dict(a=1, b=1), dict(b=2, c=2), dict(c=3, d=3), d=4, e=4)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 4}
"""
return dict((
(k,v)
for d in dicts
for k,v in d.items()
), **extra)