字典合并在字典推导中的应用

51
在Python 3.5中,我们可以使用双星号解包来合并字典。

在Python 3.5中,我们可以使用双星号解包来合并字典。

>>> 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, {}),这看起来很丑陋。为什么解析器不允许“一种明显的方法”执行,当那个表达式似乎没有任何歧义?


2
在任何其他的 * 或 ** 解包上下文中也不能这样做。也就是说,你不能这样做 some_function(*x for x in list_lists)。解包星号不是真正的运算符,不能出现在表达式中。 - BrenBarn
8
{k: v for d in [d1, d2] for k, v in d.items()} 是一个替代您的 reduce() 的方法,虽然它看起来"比较丑",但仍然有效。 - Ilja Everilä
3
我认为另一个选择是dict(ChainMap(d2, d1)),但个人不太喜欢,因为谁知道什么是ChainMap呢? - machine yearning
2
实际上,只有ChainMap(*ds)本身似乎已经足够好了!不错,你应该将其作为答案添加进来。 - wim
5
叹气... {**d for d in ds} 真的很好。 - Eric Duminil
显示剩余4条评论
6个回答

38

虽然这不完全是对你问题的回答,但我建议使用 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'}

19
对我来说,显而易见的方法是:
d_out = {}
for d in ds:
    d_out.update(d)

这是快速且可能相当高效的方法。我不知道我能否代表Python开发人员,但我不知道您期望的版本是否更易读。例如,由于缺少 :,您的理解看起来更像一个集合理解。就我个人而言,我认为没有任何技术原因(例如解析器歧义)阻止他们添加理解拆包的形式。据说,虽然已经提出了这些形式, 但它们没有普遍的支持足以保证实施它们(至少目前还没有)。

1
通过将其变成表达式而不是语句,功能风格的可能用例增加了。如果for循环总是更好,那么根本没有理由添加合并表达式 - 我的问题更多的是为什么它被有意限制为已知的预定操作数数量? - wim
@wim -- 如果你真的想用函数式方法来做这件事,那就把它包装成一个函数 :-). 正如相关PEP中所讨论的那样,有意限制它的原因是因为社区内对于使用什么语法没有足够的共识。也许将来会重新审视它,但目前为止,它被省略了,以便可以安排所有人都同意的部分进行实现。 - mgilson
1
有趣的是,理解语法实际上是实现的一部分,并已被积极删除。 - norok2
@norok2 我仍然看不到任何令人信服的理由,为什么它被移除了(?) - wim

4

使用惯用语,不需要 ChainMap

>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> {k: v for d in [d1, d2] for k, v in d.items()}
{1: 'one', 2: 'two', 3: 'three'}

2
您可以使用 itertools.chainitertools.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}

我该如何在同一行中解压它(在分配给merged_d之前)? - Iuri Guilherme

1

基于这个解决方案,并且还被@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)

0
你可以定义这个函数:
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'}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接