python:如何将两个字典合并到一个表达式中?

 2019-04-05 

我有两个Python字典,我想编写一个表达式来返回这两个字典并置。如果update()方法返回它的结果而不是修改dict,那么它就是我所需要的。

1
2
3
4
5
6
7
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

如何在z而不是x中得到最终合并的dict ?

(需要特别说明的是,我也在寻找处理dict.update()冲突的最后一种方法。)


How can I merge two Python dictionaries in a single expression?

对于字典xyz成为一个浅合并的字典,用来自y的值替换来自x的值。

在python3.5或以上版本:

1
z = {**x, **y}

在python2中,(或3.4或更低)写一个函数:

1
2
3
4
def merge_two_dicts(x, y):
    z = x.copy()   # start with x's keys and values
    z.update(y)    # modifies z with y's keys and values & returns None
    return z

现在:

1
z = merge_two_dicts(x, y)

解释

假设您有两个dict,您想将它们合并到一个新的dict中,而不更改原来的dict:

1
2
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

所需的结果是得到一个合并了值的新字典(z),第二个dict的值覆盖了第一个dict的值。

1
2
>>> z
{'a': 1, 'b': 3, 'c': 4}

为此,在PEP 448中提出了一种新的语法,并在Python 3.5中可用

1
z = {**x, **y}

这确实是一个简单的表达。

注意,我们也可以合并文字符号:

1
z = {**x, 'foo': 1, 'bar': 2, **y}

现在:

1
2
>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}

它现在显示为在3.5的发行计划中实现的PEP 478,并且现在已经进入了Python 3.5文档中的新内容。

但是,由于许多组织仍然使用Python 2,您可能希望以向后兼容的方式来实现这一点。Python 2和Python 3.0-3.4中提供的经典Python方法是将此过程分为两步:

1
2
z = x.copy()
z.update(y) # which returns None since it mutates z

在这两种方法中,y都是第二位的,它的值将替换x的值,因此'b'将在我们的最终结果中指向3

在Python 3.5中还没有,但是需要一个表达式

如果你还没有使用Python 3.5,或者需要编写向后兼容的代码,并且你想把它放在一个表达式中,那么最有效的方法就是把它放在一个函数中:

1
2
3
4
5
def merge_two_dicts(x, y):
   """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

然后你有一个简单的表达式:

1
z = merge_two_dicts(x, y)

您还可以创建一个函数来合并一个未定义的dicts数量,从0到一个非常大的数字:

1
2
3
4
5
6
7
8
9
def merge_dicts(*dict_args):
   """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
   """

    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

这个函数在python2和python3中适用于所有dict。例如,给定ag:

1
z = merge_dicts(a, b, c, d, e, f, g)

g中的键值对将优先于af的dicts,以此类推。

对其他答案的评论

不要使用你在之前被接受的答案中看到的:

1
z = dict(x.items() + y.items())

在Python 2中,您创建了两个列表为每个dict类型在内存中,在内存中创建第三个列表的长度等于前两个放在一起,然后丢弃所有三个列表创建关键字。在Python 3中,这将会失败,因为你要添加两个dict_items对象在一起,而不是两个列表

1
2
3
4
>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

您必须显式地将它们创建为列表,例如z = dict(list(x.items()) + list(y.items()))。这是对资源和计算能力的浪费。

类似地,在Python 3中使用items()的union(在Python 2.7中使用viewitems()),当值是不可清除的对象(例如列表)时也会失败。即使您的值是可hashable的,因为集合在语义上是无序的,所以该行为在优先级方面是未定义的。所以不要这样做:

1
>>> c = dict(a.items() | b.items())

这个例子演示了当值不可刷新时会发生什么:

1
2
3
4
5
6
>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

这里有一个例子,y应该具有优先级,但是由于集合的任意顺序,x的值被保留:

1
2
3
4
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

另一个你不应该使用的黑客:

1
z = dict(x, **y)

这个构造函数使用dict,是非常快,记忆效率(甚至比我们的两步过程稍微更强),但是除非你知道到底会发生什么还在这里(即第二dict作为关键字参数传递给构造函数关键字),很难读,不打算使用,因此它不是神谕的。

下面是在django中纠正这种用法的一个例子。

dict的目的是获取可替换的键(例如frozensets或tuple),但是当键不是字符串时,这种方法在python3中会失败。

1
2
3
4
>>> c = dict(a, **b)
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

在邮件列表中,该语言的创建者Guido van Rossum写道:

I am fine with
declaring dict({}, **{1:3}) illegal, since after all it is abuse of
the ** mechanism.

Apparently dict(x, **y) is going around as"cool hack" for"call
x.update(y) and return x". Personally I find it more despicable than
cool.

根据我的理解(以及该语言的创建者的理解),dict(**y)的预期用途是为可读性目的创建词典,例如:

1
dict(a=1, b=10, c=11)

而不是

1
{'a': 1, 'b': 10, 'c': 11}

回应评论

Despite what Guido says, dict(x, **y) is in line with the dict specification, which btw. works for both Python 2 and 3. The fact that this only works for string keys is a direct consequence of how keyword parameters work and not a short-comming of dict. Nor is using the ** operator in this place an abuse of the mechanism, in fact ** was designed precisely to pass dicts as keywords.

同样,当键是非字符串时,它对3不起作用。隐式调用契约是名称空间采用普通的dict,而用户必须只传递字符串关键字参数。所有其他可调用项都强制执行它。dict在Python 2中打破了这种一致性:

1
2
3
4
5
6
>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

考虑到Python的其他实现(Pypy、Jython、IronPython),这种不一致性很糟糕。因此,它在python3中得到了修复,因为这种用法可能是一个突破性的变化。

我向您提交的意见是,故意编写只在一种语言的一个版本中有效的代码,或者只在特定的任意约束下有效的代码,这是恶意的无能。

更多的评论:

dict(x.items() + y.items()) is still the most readable solution for Python 2. Readability counts.

我的回答是:merge_two_dicts(x, y)实际上对我来说似乎更清楚,如果我们真的关心可读性的话。而且它不向前兼容,因为Python 2越来越不受欢迎。

{**x, **y} does not seem to handle nested dictionaries. the contents of nested keys are simply overwritten, not merged [...] I ended up being burnt by these answers that do not merge recursively and I was surprised no one mentioned it. In my interpretation of the word"merging" these answers describe"updating one dict with another", and not merging.

是的。我必须让您回到这个问题,它要求两个字典进行浅合并,第一个字典的值被第二个字典的值覆盖——在一个表达式中。

假设有两个字典字典,其中一个可能递归地将它们合并到一个函数中,但是您应该注意不要修改来自任何一个源的字典,避免这种情况的最可靠的方法是在赋值时进行复制。由于密钥必须是可加锁的,因此通常是不可变的,复制它们是没有意义的:

1
2
3
4
5
6
7
8
9
10
11
12
from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() &amp; y.keys()
    for key in overlapping_keys:
        z[key] = dict_of_dicts_merge(x[key], y[key])
    for key in x.keys() - overlapping_keys:
        z[key] = deepcopy(x[key])
    for key in y.keys() - overlapping_keys:
        z[key] = deepcopy(y[key])
    return z

用法:

1
2
3
4
>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

为其他值类型提出偶然性远远超出了这个问题的范围,所以我将向您指出我对"字典中的字典合并"这一规范问题的回答。

性能较差,但正确的Ad-hocs

这些方法的性能较差,但是它们将提供正确的行为。它们的性能将远低于copyupdate或新的解压缩,因为它们在更高的抽象级别上迭代每个键值对,但是它们确实遵循优先级的顺序(后面的dict具有优先级)

您还可以在一个dict理解中手动链接dict:

1
{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

或者在python 2.6中(可能早在2.4引入生成器表达式时):

1
dict((k, v) for d in dicts for k, v in d.items())

itertools.chain将迭代器按正确的顺序链接到键值对上:

1
2
import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

性能分析

我只打算对已知的行为正确的用法进行性能分析。

1
import timeit

下面是在Ubuntu 14.04上完成的

在Python 2.7 (system Python)中:

1
2
3
4
5
6
7
8
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

在python3.5 (deadsnake PPA)中:

1
2
3
4
5
6
7
8
9
10
>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

资源字典我对Python字典实现的解释,更新为3.6。回答如何向字典中添加新键将两个列表映射到一个字典中关于字典的官方Python文档布兰登·罗兹2017年在Pycon大会上的演讲更有力《现代Python词典》,汇集了许多伟大的思想——Raymond Hettinger在2017 Pycon大会上的演讲


在你的情况下,你可以做的是:

1
z = dict(x.items() + y.items())

如您所愿,这将把最后一个dict放入z,并使key b的值被第二个(y) dict的值正确地覆盖:

1
2
3
4
5
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

如果使用python3,它只会稍微复杂一点。创建z:

1
2
3
>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

另一个:

1
2
z = x.copy()
z.update(y)


另一个更简洁的选项是:

1
z = dict(x, **y)

注意:这已经成为一个流行的答案,但重要的是要指出,如果y有任何非字符串键,那么这完全可以工作的事实是对CPython实现细节的滥用,而且它在python3、PyPy、IronPython或Jython中不能工作。此外,圭多也不是球迷。因此,我不推荐这种技术用于向前兼容或跨实现的可移植代码,这实际上意味着应该完全避免这种技术。


这可能不是一个流行的答案,但是您几乎肯定不希望这样做。如果您想要一个合并的副本,那么使用copy(或deepcopy,这取决于您想要什么),然后更新。与使用.items() + .items()创建单行代码相比,这两行代码更具可读性——更符合python风格。显式比隐式好。

此外,当您使用.items() (pre - Python 3.0)时,您将创建一个包含dict项的新列表。update()可以更有效地工作,因为它可以逐项运行第二个dict。

在时间方面:

1
2
3
4
5
6
7
8
9
10
>>> timeit.Timer("dict(x, **y)","x = dict(zip(range(1000), range(1000)))
y=dict(zip(range(1000,2000), range(1000,2000)))"
).timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()
temp.update(y)"
,"x = dict(zip(range(1000), range(1000)))
y=dict(zip(range(1000,2000), range(1000,2000)))"
).timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())","x = dict(zip(range(1000), range(1000)))
y=dict(zip(range(1000,2000), range(1000,2000)))"
).timeit(100000)
41.484580039978027

在我看来,前两者之间的微小减速是值得的可读性。此外,用于创建字典的关键字参数仅在Python 2.3中添加,而copy()和update()在较早的版本中可以工作。


在后续的回答中,您询问了这两种方法的相对性能:

1
2
z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

至少在我的机器上(一个相当普通的运行Python 2.5.2的x86_64), alternative z2不仅更短、更简单,而且速度更快。您可以使用Python附带的timeit模块自己验证这一点。

例1:相同的字典将20个连续整数映射到它们自己:

1
2
3
4
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)'
100000 loops, best of 3: 1.53 usec per loop

z2以3.5倍左右的优势获胜。不同的字典似乎会产生不同的结果,但是z2似乎总是在前面。(如果在相同的测试中得到不一致的结果,请尝试传入-r,其值大于默认值3。)

例2:非重叠字典将252个短字符串映射为整数,反之亦然:

1
2
3
4
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'              
10000 loops, best of 3: 26.9 usec per loop

z2以大约10倍的优势获胜。在我看来,这是一个相当大的胜利!

在比较了这两个项目之后,我想知道z1的糟糕性能是否可以归因于构造这两个项目列表的开销,这反过来又让我想知道这种变化是否可以更好地工作:

1
2
from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

一些快速的测试,例如。

1
2
% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

让我得出这样的结论:z3z1稍微快一些,但没有z2快。绝对不值得这么多打字。

这个讨论仍然缺少一些重要的东西,即将这些替代方案与合并两个列表的"明显"方式(使用update方法)进行性能比较。为了使表达式与没有修改x或y的表达式保持平等,我将复制x而不是修改它,如下所示:

1
2
z0 = dict(x)
z0.update(y)

一个典型的结果:

1
2
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

换句话说,z0z2似乎具有本质上相同的性能。你认为这可能是个巧合吗?我不....

事实上,我甚至认为纯Python代码不可能做得比这更好。如果您可以在C扩展模块中做得更好,我想Python人员可能会对将您的代码(或方法的变体)合并到Python核心中很感兴趣。Python在很多地方使用dict;优化其运营是一件大事。

你也可以这样写

1
2
z0 = x.copy()
z0.update(y)

正如Tony所做的那样,但是(毫不奇怪)符号上的差异对性能没有任何可测量的影响。用你认为合适的。当然,他指出两句话的版本更容易理解是绝对正确的。


我想要类似的东西,但是能够指定如何合并重复键上的值,所以我删除了这个(但没有进行大量测试)。显然,这不是一个表达式,而是一个函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def merge(d1, d2, merge_fn=lambda x,y:y):
   """
    Merges two dictionaries, non-destructively, combining
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous
    types in your dicts, so specifying a merge technique can be
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

   """

    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

在python3中,您可以使用集合。ChainMap将多个dicts或其他映射组合在一起,创建一个单一的、可更新的视图:

1
2
3
4
5
6
7
8
9
10
>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = ChainMap({}, y, x)
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11


递归/深度更新dict

1
2
3
4
5
6
7
8
9
10
11
def deepupdate(original, update):
   """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
   """

    for key, value in original.iteritems():
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key])
    return update

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

输出:

1
2
3
4
5
6
7
{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

感谢rednaw的编辑。


在不使用copy的情况下,我能想到的最好的版本是:

1
2
3
4
from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

它比dict(x.items() + y.items())快,但没有n = copy(a); n.update(b)快,至少在CPython上是这样。如果您将iteritems()更改为items(),这个版本在python3中也可以工作,这是由2to3工具自动完成的。

我个人最喜欢这个版本,因为它用一个函数语法很好地描述了我想要的东西。唯一的小问题是,y的值优先于x的值并不是很明显,但我不认为这很难算出来。


Python 3.5 (PEP 448)允许更好的语法选项:

1
2
3
4
5
x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y}
final
# {'a': 2, 'b': 1, 'c': 2}

甚至

1
final = {'a': 1, 'b': 1, **x, **y}


1
2
3
4
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

对于在两个字典中都有键的项('b'),您可以通过将该项放在最后来控制输出中的哪个项。


虽然这个问题已经被回答过好几次了,这个简单的解决方案还没有列出。

1
2
3
4
5
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

它和上面提到的z0和邪恶的z2一样快,但是很容易理解和改变。


1
2
3
4
5
6
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

在这些可疑的答案中,这个闪亮的例子是唯一一个也是唯一一个在Python中合并dicts的好方法,它得到了独裁者Guido van Rossum本人的支持!还有人提出了一半的建议,但没有把它放入函数中。

1
2
3
print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

给:

1
{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}


如果你认为拉姆达斯是邪恶的,那就不要再读下去了。根据要求,你可以用一个表达式写出快速和内存效率高的解决方案:

1
2
3
4
5
6
7
x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

如上所述,使用两行代码或编写函数可能是更好的方法。


神谕的。使用一种理解:

1
2
3
4
z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}


在python3中,items方法不再返回列表,而是返回一个视图,它的作用类似于一个集合。

1
dict(x.items() | y.items())

对于2.7版本中类似python的行为,viewitems方法应该代替items:

1
dict(x.viewitems() | y.viewitems())

无论如何,我更喜欢这种表示法,因为将它看作集合联合操作而不是连接(如标题所示)似乎更自然。

编辑:

python 3还有几个要点。首先,请注意,除非y中的键是字符串,否则dict(x, **y)技巧在python 3中无法工作。

此外,Raymond Hettinger的Chainmap答案相当优雅,因为它可以接受任意数量的dict作为参数,但是从文档来看,它看起来像是依次查看每个查找的所有dict列表:

Lookups search the underlying mappings successively until a key is found.

如果你在你的应用程序中有很多查找,这可能会减慢你的速度:

1
2
3
4
5
6
7
8
In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 μs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 μs per loop

所以查找速度要慢一个数量级。我是Chainmap的粉丝,但是在有很多查找的地方看起来不太实用。


虐待导致马修的答案只有一种表达方式:

1
2
3
4
5
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

您说需要一个表达式,所以我滥用了lambda来绑定名称,并使用元组来覆盖lambda的一个表达式限制。你尽管畏缩吧。

当然,如果你不介意复制的话,你也可以这样做:

1
2
3
4
5
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}

使用保持顺序的itertools的简单解决方案(后面的词具有优先级)

1
2
import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

的用法:

1
2
3
4
5
6
7
8
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}

两本词典

1
2
def union2(dict1, dict2):
    return dict(list(dict1.items()) + list(dict2.items()))

n字典

1
2
def union(*dicts):
    return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))

sum性能很差。参见https://mathieularose.com/how-not-to-flatten-a-list-of-lists-in-python/


在python 3:

1
2
3
4
5
6
7
import collections
a = {1: 1, 2: 2}
b = {2: 3, 3: 4}
c = {3: 5}

r = dict(collections.ChainMap(a, b, c))
print(r)

:

1
{1: 1, 2: 2, 3: 4}

文档:https://docs.python.org/3/library/collections.html # collections.ChainMap:


即使答案对于这个浅字典很好,这里定义的所有方法实际上都没有做深字典合并。

例子:

1
2
3
a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

人们会期待这样的结果:

1
{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

相反,我们得到:

1
{'two': True, 'one': {'extra': False}}

如果"one"条目确实是一个合并项,那么它的字典中应该有"depth_2"和"extra"作为条目。

使用链也,不工作:

1
2
from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

结果:

1
{'two': True, 'one': {'extra': False}}

rcwesick给出的深度合并也产生了相同的结果。

是的,合并示例字典是可行的,但是它们都不是通用的合并机制。稍后,当我编写一个真正进行合并的方法时,我将对此进行更新。


对于python2:

1
2
3
4
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items()+y.items())
print(z)

Python 3:

1
2
3
4
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items()|y.items())
print(z)

它给输出:{'a': 1, 'c': 11, 'b': 10}


利用这里和其他地方的想法,我理解了一个函数:

1
2
def merge(*dicts, **kv):
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

用法(在python3中测试):

1
2
3
4
5
6
7
8
9
assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

你可以用代替。


这可以用一个单词理解:

1
2
3
4
5
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

在我看来,"单一表达式"部分的最佳答案是不需要额外的函数,而且它很短。


到目前为止,我所列出的解决方案的问题是,在合并的字典中,键"b"的值是10,但是在我看来,它应该是12。为此,我提出以下建议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import timeit

n=100000
su ="""
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""


def timeMerge(f,su,niter):
    print"{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print"confirm b elements are added:",x

结果:

1
2
3
4
5
6
0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                  
0.150380 sec for: dict(x.items() + y.items())  
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}


1
2
3
4
5
6
7
8
9
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}


在Python 3.5中,您可以使用unpack **来创建新的字典。这种方法在过去的答案中没有被证明。此外,最好使用{}而不是dict()。因为{}是python文字,而dict()涉及函数调用。

1
2
3
4
5
dict1 = {'a':1}
dict2 = {'b':2}
new_dict = {**dict1, **dict2}
>>>new_dict
{'a':1, 'a':2}


1
2
3
4
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

这应该能解决你的问题。


(仅供Python2.7 *;Python3*有更简单的解决方案。)

如果您不反对导入标准库模块,您可以这样做

1
2
3
4
from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(lambda中的or a位是必要的,因为dict.update总是在成功时返回None。)


您可以为此使用toolz.merge([x, y])


太愚蠢了,.update什么也不返回。我只是用一个简单的辅助函数来解决这个问题:

1
2
3
4
def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

例子:

1
2
3
4
merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

使用单词理解,你可以

1
2
3
4
5
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}

dc = {xi:(x[xi] if xi not in list(y.keys())
           else y[xi]) for xi in list(x.keys())+(list(y.keys()))}

给了

1
2
>>> dc
{'a': 1, 'c': 11, 'b': 10}

注意理解中的if else语法

1
2
{ (some_key if condition else default_key):(something_if_true if condition
          else something_if_false) for key, value in dict_.items() }


OP的两本词典的结合应该是这样的:

1
{'a': 1, 'b': 2, 10, 'c': 11}

具体地说,两个实体(xy)的联合包含x和/或y的所有元素。不幸的是,尽管这篇文章的标题是工会,但OP要求的并不是工会。

我下面的代码既不优雅也不是一行代码,但是我相信它与union的含义是一致的。

从OP的例子中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}

z = {}
for k, v in x.items():
    if not k in z:
        z[k] = [(v)]
    else:
        z[k].append((v))
for k, v in y.items():
    if not k in z:
        z[k] = [(v)]
    else:
        z[k].append((v))

{'a': [1], 'b': [2, 10], 'c': [11]}

是否需要列表可以更改,但是如果字典中包含列表(和嵌套列表)作为任何一个字典中的值,那么上面的方法都是有效的。


我知道这并不真正符合问题的具体情况("一行代码"),但是由于上面的答案都没有涉及这个方向,而大量的答案都解决了性能问题,所以我觉得我应该贡献我的想法。

根据用例,可能没有必要为给定的输入字典创建一个"真正的"合并字典。在许多情况下,这样做的视图可能就足够了,也就是说,像合并字典一样工作的对象不需要完全计算它。可以这么说,这是合并字典的惰性版本。

在Python中,这相当简单,可以使用我文章末尾所示的代码来完成。在此前提下,原问题的答案将是:

1
z = MergeDict(x, y)

当使用这个新对象时,它将表现得像一个合并的字典,但是它将有固定的创建时间和固定的内存占用,而原始字典则保持不变。创建它比提出的其他解决方案要便宜得多。

当然,如果您经常使用这个结果,那么您将在某个时候达到一个极限,创建一个真正的合并字典将是更快的解决方案。正如我所说,这取决于您的用例。

如果您曾经想要一个真正的合并的dict,那么调用dict(z)将产生它(当然,这比其他解决方案要昂贵得多,所以这只是值得一提)。

你也可以使用这个类来制作一种"写中复制"的字典:

1
2
3
4
5
a = { 'x': 3, 'y': 4 }
b = MergeDict(a)  # we merge just one dict
b['x'] = 5
print b  # will print {'x': 5, 'y': 4}
print a  # will print {'y': 4, 'x': 3}

下面是MergeDict的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class MergeDict(object):
  def __init__(self, *originals):
    self.originals = ({},) + originals[::-1]  # reversed

  def __getitem__(self, key):
    for original in self.originals:
      try:
        return original[key]
      except KeyError:
        pass
    raise KeyError(key)

  def __setitem__(self, key, value):
    self.originals[0][key] = value

  def __iter__(self):
    return iter(self.keys())

  def __repr__(self):
    return '%s(%s)' % (
      self.__class__.__name__,
      ', '.join(repr(original)
          for original in reversed(self.originals)))

  def __str__(self):
    return '{%s}' % ', '.join(
        '%r: %r' % i for i in self.iteritems())

  def iteritems(self):
    found = set()
    for original in self.originals:
      for k, v in original.iteritems():
        if k not in found:
          yield k, v
          found.add(k)

  def items(self):
    return list(self.iteritems())

  def keys(self):
    return list(k for k, _ in self.iteritems())

  def values(self):
    return list(v for _, v in self.iteritems())


如果您不介意修改x

1
x.update(y) or x

简单,可读性高,性能。您知道update()总是返回None,这是一个错误的值。所以它的值总是x

在标准库中修改方法,比如update,按照约定返回None,所以这个技巧也适用于这些方法。

如果您使用的库不遵循这种约定,您可以使用tuple display和index使其成为单个表达式,而不是or,但是它的可读性不强。

1
(x.update(y), x)[-1]

如果变量中还没有x,可以使用lambda在不使用赋值语句的情况下生成局部变量。这相当于使用lambda作为let表达式,这是函数式语言中的一种常见技术,但并不符合python的风格。

1
(lambda x: x.update(y) or x)({'a':1, 'b': 2})

如果您确实想要一个副本,PEP 448是最好的{**x, **y}。但如果没有,让我们也在这里工作。

1
(lambda z: z.update(y) or z)(x.copy())

我很好奇,我是否可以用一行字符串化的方法来击败这个被接受的答案:

我尝试了5种方法,之前都没有提到——都是一行代码——都给出了正确的答案——但我无法接近。

所以…为了省去你的麻烦,也许满足你的好奇心:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import json
import yaml
import time
from ast import literal_eval as literal

def merge_two_dicts(x, y):
    z = x.copy()   # start with x's keys and values
    z.update(y)    # modifies z with y's keys and values &amp; returns None
    return z

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}

start = time.time()
for i in range(10000):
    z = yaml.load((str(x)+str(y)).replace('}{',', '))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify yaml')

start = time.time()
for i in range(10000):
    z = literal((str(x)+str(y)).replace('}{',', '))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify literal')

start = time.time()
for i in range(10000):
    z = eval((str(x)+str(y)).replace('}{',', '))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify eval')

start = time.time()
for i in range(10000):
    z = {k:int(v) for k,v in (dict(zip(
            ((str(x)+str(y))
            .replace('}',' ')
            .replace('{',' ')
            .replace(':',' ')
            .replace(',',' ')
            .replace("'",'')
            .strip()
            .split('  '))[::2],
            ((str(x)+str(y))
            .replace('}',' ')
            .replace('{',' ').replace(':',' ')
            .replace(',',' ')
            .replace("'",'')
            .strip()
            .split('  '))[1::2]
             ))).items()}
elapsed = (time.time()-start)
print (elapsed, z, 'stringify replace')

start = time.time()
for i in range(10000):
    z = json.loads(str((str(x)+str(y)).replace('}{',', ').replace("'",'"')))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify json')

start = time.time()
for i in range(10000):
    z = merge_two_dicts(x, y)
elapsed = (time.time()-start)
print (elapsed, z, 'accepted')

结果:

1
2
3
4
5
6
7.693928956985474 {'c': 11, 'b': 10, 'a': 1} stringify yaml
0.29134678840637207 {'c': 11, 'b': 10, 'a': 1} stringify literal
0.2208399772644043 {'c': 11, 'b': 10, 'a': 1} stringify eval
0.1106564998626709 {'c': 11, 'b': 10, 'a': 1} stringify replace
0.07989692687988281 {'c': 11, 'b': 10, 'a': 1} stringify json
0.005082368850708008 {'c': 11, 'b': 10, 'a': 1} accepted

我从中学到的是json方法是(那些尝试的)从字典的字符串返回字典的最快方法;比我认为的使用ast的常规方法快得多(大约是四分之一的时间)。我还了解到,应不惜一切代价避免使用yaml方法。

是的,我知道这不是最好的/正确的方式,所以请不要拒绝投票否定遗忘,零就好。我很好奇它是不是更快,但事实并非如此;我发帖就是为了证明这一点。


这个问题被标记为python-3x,但是,考虑到它是一个相对较新的添加,而且大多数被投票接受的答案广泛地处理Python 2。x解决方案,我敢添加一个使用Python 2的烦人特性的行程序。x列表理解,即名称泄露…

1
2
3
4
5
6
7
8
9
10
11
$ python2
Python 2.7.13 (default, Jan 19 2017, 14:48:08)
[GCC 6.3.0 20170118] on linux2
Type"help","copyright","credits" or"license" for more information.
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> [z.update(d) for z in [{}] for d in (x, y)]
[None, None]
>>> z
{'a': 1, 'c': 11, 'b': 10}
>>> ...

我很高兴地说,上面的方法在python3的任何版本上都不再适用了。


这是Python 3.5或更高版本的表达式,使用reduce合并字典:

1
2
3
4
>>> from functools import reduce
>>> l = [{'a': 1}, {'b': 2}, {'a': 100, 'c': 3}]
>>> reduce(lambda x, y: {**x, **y}, l, {})
{'a': 100, 'b': 2, 'c': 3}

注意:即使字典列表是空的或者只包含一个元素,也可以这样做。


我想我那些丑陋的俏皮话在这里是必要的。

1
2
3
z = next(z.update(y) or z for z in [x.copy()])
# or
z = (lambda z: z.update(y) or z)(x.copy())

字典是合并。单一的表达式。永远不要使用它。

附注:这是一个可以在Python的两个版本中工作的解决方案。我知道python3有这个{**x, **y}的东西,它是正确的东西(如果你仍然有python2,那么迁移到python3是正确的事情)。


由于PEP 572:赋值表达式,Python 3.8发行版(计划于2019年10月20日发布)将提供一个新选项。新的赋值表达式运算符:=允许您分配copy的结果,并仍然使用它调用update,使合并后的代码只剩下一个表达式,而不是两个语句,更改如下:

1
2
newdict = dict1.copy()
newdict.update(dict2)

:

1
(newdict := dict1.copy()).update(dict2)

却在各方面表现相同。如果您还必须返回结果dict(您要求一个表达式返回dict;上面的创建和分配newdict,但是不返回它,所以你不能用它来传递一个函数的参数,la myfunc((newdict := dict1.copy()).update(dict2))),然后添加or newdict结束(自None update回报,falsy,它将评估和返回newdict表达式的结果):

1
(newdict := dict1.copy()).update(dict2) or newdict

重要提示:一般来说,我不建议使用这种方法,而是支持:

1
newdict = {**dict1, **dict2}

解包方法清晰(谁知道广义开箱首先,你应该),根本不需要结果的名称(因此更简洁,当建立一个临时立即传递给一个函数或包含在list / tuple文字等),以及几乎肯定是快,(在CPython的)大致相当于:

1
2
3
newdict = {}
newdict.update(dict1)
newdict.update(dict2)

但是在C层使用具体的dict API完成,因此不涉及动态方法查找/绑定或函数调用分派开销(其中(newdict := dict1.copy()).update(dict2)在行为上不可避免地与原来的两行程序相同,以离散步骤执行工作,使用方法的动态查找/绑定/调用。

它也更具扩展性,因为合并三个dict是显而易见的:

1
 newdict = {**dict1, **dict2, **dict3}

使用赋值表达式不会像那样缩放;你能得到的最接近的答案是:

1
 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

或者不使用Nones的临时元组,但对每个None结果进行真实性测试:

1
 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

显然,这两种方法中的任何一种都更加丑陋,并且还包括进一步的低效性(要么浪费一个临时的None用于逗号分隔,要么对每个updateNone返回的or进行无意义的真实性测试)。

赋值表达式方法的唯一真正优势出现在以下情况:

您有一个通用代码,它需要同时处理setdict(它们都支持copyupdate,所以代码的工作方式与您所期望的大致相同)您希望接收任意类似于字典的对象,而不仅仅是dict本身,并且必须保留左侧的类型和语义(而不是以一个普通的dict结束)。虽然myspecialdict({**speciala, **specialb})可能有效,但它将涉及额外的临时dict,如果myspecialdict具有普通dict无法保存的特性(例如,正则dict现在根据键的第一次出现来保存顺序,根据键的最后一次出现来保存值;您可能想要一个基于键的最后一次出现保持顺序的键,以便更新值也将其移动到末尾),那么语义将是错误的。由于赋值表达式版本使用指定的方法(假定重载了这些方法以使其行为适当),所以它根本不创建dict(除非dict1已经是一个dict),保留原始类型(和原始类型的语义),同时避免任何临时类型。


我认为你应该做以下事情:

1
z = dict(x.items() + y.items())

我有一个没有在这里指定的解决方案(Man I LOVE python):-)

1
2
z = {}
z.update(x) or z.update(y)

这不会像更新y一样更新x。我认为它不会慢得可怕。

注意:它应该是'or' operation and not 'and' operation。编辑以纠正代码。


一个快速的解决方案是:

1
z4 = x.copy().update(y)