如何在Python中的单个表达式中合并两个字典?


4780

我有两个Python字典,我想编写一个返回合并的这两个字典的表达式。update()如果返回结果而不是就地修改字典,该方法将是我所需要的。

>>> 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.update()我正在寻找的最后一个胜出的冲突处理方法是。)


如果您使用的是Python 3.9 alpha,那么请使用z = x | y
The Daleks

Answers:


5682

如何在一个表达式中合并两个Python字典?

对于字典xyz变成了浅层合并的字典,带有y替换的值x

  • 在Python 3.5或更高版本中:

    z = {**x, **y}
  • 在Python 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

    现在:

    z = merge_two_dicts(x, y)
  • 在Python 3.9.0a4以上(最终发布日期大约2020年10月):PEP-584这里讨论,执行,以进一步简化这一点:

    z = x | y          # NOTE: 3.9+ ONLY

说明

假设您有两个字典,并且想要将它们合并为新字典而不更改原始字典:

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

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

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

PEP 448中提出并从Python 3.5开始可用的新语法是

z = {**x, **y}

它确实是一个表达。

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

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

现在:

>>> 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中可用的经典Pythonic方法是分两个步骤完成的:

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

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

尚未在Python 3.5上运行,但需要一个表达式

如果您尚未使用Python 3.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

然后您有一个表达式:

z = merge_two_dicts(x, y)

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

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

此函数将在Python 2和3中适用于所有字典。例如给以下a命令g

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

和中的键值对g优先af,以此类推。

其他答案的批判

不要使用以前接受的答案中看到的内容:

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

在Python 2中,您将在每个内存字典中创建两个列表,在内存中创建第三个列表,其长度等于前两个字典的长度,然后丢弃所有三个列表以创建字典。在Python 3中,这将失败,因为您将两个dict_items对象而不是两个列表加在一起-

>>> 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()))。这浪费了资源和计算能力。

类似地,当值是不可散列的对象(例如列表)时,items()在Python 3(viewitems()在Python 2.7中)进行联合也将失败。即使您的值是可哈希的,由于集合在语义上是无序的,因此关于优先级的行为是不确定的。所以不要这样做:

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

此示例演示了值不可散列时会发生的情况:

>>> 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的值:

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

您不应该使用的另一种技巧:

z = dict(x, **y)

这使用了dict构造函数,并且非常快速且具有内存效率(甚至比我们的两步过程略高),但是除非您确切地知道这里正在发生什么(也就是说,第二个dict作为关键字参数传递给dict,构造函数),很难阅读,它不是预期的用法,因此不是Pythonic。

这是在django修复的用法示例。

字典旨在获取可散列的键(例如,frozenset或元组),但是当键不是字符串时此方法在Python 3中失败。

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

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

我宣布dict({},** {1:3})非法是可以的,因为毕竟这是对**机制的滥用。

显然dict(x,** y)被“调用x.update(y)并返回x”的“酷砍”。我个人觉得它比酷更卑鄙。

根据我的理解(以及对语言创建者的理解),该命令的预期用途dict(**y)是用于创建可读性强的命令,例如:

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

代替

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

对评论的回应

尽管Guido说了什么dict(x, **y),但符合dict规范,顺便说一句。它仅适用于Python 2和3。事实上,这仅适用于字符串键,这是关键字参数如何工作的直接结果,而不是字典的简称。在这个地方使用**运算符也不会滥用该机制,实际上**正是为了将dict作为关键字传递而设计的。

同样,当键为非字符串时,它不适用于3。隐式调用协定是名称空间采用普通命令,而用户只能传递字符串形式的关键字参数。所有其他可调用对象都强制执行了它。dict在Python 2中破坏了这种一致性:

>>> 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),这种不一致是很糟糕的。因此,它在Python 3中已得到修复,因为这种用法可能是一个重大更改。

我向您指出,故意编写仅在一种语言版本中有效或仅在特定的任意约束下有效的代码是恶意的无能。

更多评论:

dict(x.items() + y.items()) 仍然是Python 2最具可读性的解决方案。可读性至关重要。

我的回答:merge_two_dicts(x, y)如果我们实际上担心可读性,实际上对我来说似乎更加清晰。而且它不向前兼容,因为Python 2越来越不推荐使用。

{**x, **y}似乎不处理嵌套字典。嵌套键的内容只是被覆盖,而不是被合并。我最终被这些没有递归合并的答案所烧死,令我惊讶的是,没有人提到它。在我对“合并”一词的解释中,这些答案描述的是“将一个词典与另一个词典更新”,而不是合并。

是。我必须回头再问这个问题,它要求两个字典进行浅层合并,第一个字典的值由第二个字典覆盖-在一个表达式中。

假设有两个字典,一个字典可能会递归地将它们合并到一个函数中,但是您应注意不要从任何一个源修改字典,避免这种情况的最可靠方法是在分配值时进行复制。由于键必须是可散列的,因此通常是不可变的,因此复制它们毫无意义:

from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() & 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

用法:

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

提出其他值类型的偶发性问题远远超出了此问题的范围,因此,我将为您回答有关“词典合并字典”的规范问题的答案

性能较差但临时性正确

这些方法的性能较差,但是它们将提供正确的行为。他们将少得多比高性能copyupdate或新的拆包,因为他们通过在更高的抽象水平的每个键-值对迭代,但他们做的尊重优先顺序(后者类型的字典具有优先权)

您还可以在dict理解内手动将dict链接:

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

或在python 2.6中(也许在引入生成器表达式时早在2.4中):

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

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

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

绩效分析

我将仅对已知行为正确的用法进行性能分析。

import timeit

在Ubuntu 14.04上完成以下操作

在Python 2.7(系统Python)中:

>>> 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

在Python 3.5中(死神PPA):

>>> 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

词典资源


9
@MohammadAzim“仅字符串”仅适用于可调用项中的关键字参数扩展,不适用于广义的解包语法。为了证明此方法有效:{**{(0, 1):2}}->{(0, 1): 2}
亚伦·霍尔

36
简短的答案就像z = {**x, **y}真的刺激了我
pcko1

1
接受PEP-0584时,可能会更改。一个新的联合运算符将使用以下语法实现:x | y
Callam Delaney

2
如果答案的顶部需要摘要,则说明时间太长。
Gringo Suave

2
嗨,最上面是一个摘要,是的。由你决定。整个过程将是一篇很棒的博客文章。注意Py 3.4及以下版本是EOL,3.5将于2020-09年接近EOL。
Gringo Suave

1615

您的情况是:

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

可以根据需要将最终的dict放入中z,并使key的值b被第二(y)dict的值正确覆盖:

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

如果您使用Python 3,它只会稍微复杂一点。创建z

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

如果您使用Python版本3.9.0a4或更高版本,则可以直接使用:

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

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

1
不要使用它,因为它效率很低。(请参阅下面的timeit结果。)如果没有选择包装函数,则可能需要在Py2天内,但是现在已经过去了。
Gringo Suave

632

替代:

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

83
为了阐明为什么这不符合问题提供的条件:它不是单个表达式,并且不返回z。
Alex

2
@neuronet每个单行代码通常只是将必须发生的代码移动到不同的组件中并在那里解决它。这绝对是其中一种情况。但是其他语言在这方面比python更好。并具有返回其元素的参照透明变体是一件很高兴的事情。
亚历克斯(Alex)

12
这样说:如果您需要在两行注释中向向您解释代码的人员说明一行代码,那么您真的可以一行完成吗?:)我完全同意Python对此并不擅长:应该有一种更简单的方法。虽然这个答案更像是Python,但它真的是那么明确或清晰吗?Update不是人们经常使用的“核心”功能之一。
eric

好吧,如果人们坚持要使其成为一体,那么您可以随时进行(lambda z: z.update(y) or z)(x.copy()):P
towr

339

另一个更简洁的选择:

z = dict(x, **y)

注意:这已经成为一个流行的答案,但必须指出的是,如果y有任何非字符串键,那么它实际上是对CPython实现细节的滥用,并且在Python 3中不起作用,或在PyPy,IronPython或Jython中。另外,Guido也不是粉丝。因此,我不建议将此技术用于前向兼容或交叉实现的可移植代码,这实际上意味着应完全避免使用它。


在Python 3和PyPy和PyPy 3中都能正常工作,不能与Jython或Iron通话。鉴于已明确记录了这种模式(请参阅本文档中的第三种构造函数形式),我认为这不是“实现细节”,而是有意使用的功能。
amcgregor

5
@amcgregor您错过了关键字“如果y有任何非字符串键”。那是在Python3中不起作用的原因。它可以在CPython 2中运行的事实是不能依赖的实现细节。如果您保证所有键都是字符串,这是一个完全受支持的选项。
卡尔·梅耶

213

这可能不是一个流行的答案,但是您几乎可以肯定不想这样做。如果要合并的副本,请使用copy(或deepcopy,具体取决于您的需求),然后进行更新。与使用.items()+ .items()进行单行创建相比,两行代码更具可读性-更具Python风格。显式胜于隐式。

此外,当您使用.items()(Python 3.0之前的版本)时,您正在创建一个新列表,其中包含字典中的项目。如果您的词典很大,那将是很大的开销(创建合并字典后将立即丢弃两个大列表)。update()可以更高效地工作,因为它可以逐项执行第二个字典。

时间方面

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=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)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO出于可读性考虑,前两者之间的微小速度下降是值得的。此外,仅在Python 2.3中添加了用于字典创建的关键字参数,而copy()和update()将在较早的版本中运行。


149

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

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

至少在我的机器上(运行Python 2.5.2的相当普通的x86_64),替代z2方法不仅更短,更简单,而且显着更快。您可以使用timeitPython随附的模块自行验证。

示例1:相同的字典将20个连续的整数映射到自身:

% 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个短字符串映射为整数,反之亦然:

% 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完这两个项目后,我想知道的不佳表现是否可以归因于构建两个项目列表的开销,这反过来又让我想知道这种变化是否会更好:

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

一些快速测试,例如

% 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

我得出的结论是,z3它的速度要比速度快z1,但不及速度z2。绝对不值得所有额外的打字。

讨论中仍然缺少一些重要的内容,这是将这些替代方法与合并两个列表的“明显”方法的性能比较:使用该update方法。为了使事情与表达式保持一致,而不会修改x或y,我将制作x的副本,而不是就地对其进行修改,如下所示:

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

典型结果:

% 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

换句话说,z0并且z2似乎具有基本相同的性能。您认为这可能是巧合吗?我不....

实际上,我什至声称纯粹的Python代码不可能做到比这更好。而且,如果您可以在C扩展模块中做得更好,我想Python人士可能会对将您的代码(或方法的变体)并入Python核心感兴趣。Python dict在很多地方都使用过;优化运营非常重要。

您也可以这样写

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

就像Tony一样,但是(并不奇怪)表示法上的差异对性能没有任何可测量的影响。使用对您而言合适的任何一种。当然,他绝对正确地指出,两语句版本更容易理解。


5
这在Python 3中不起作用。items()是不可连接的,并且iteritems不存在。
Antti Haapala'Mar

126

在Python 3.0及更高版本中,您可以使用collections.ChainMap将多个字典或其他映射组合在一起的方式来创建一个可更新的视图:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(ChainMap({}, y, x))
>>> for k, v in z.items():
        print(k, '-->', v)

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

适用于Python 3.5和更高版本的更新:可以使用PEP 448扩展词典打包和拆包。快速简便:

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

3
但是使用ChainMap时应谨慎,有一个陷阱,即如果您有重复的键,则会使用第一个映射的值,并且当您调用delon 时,ChainMap c会删除该键的第一个映射。
Slayer

7
@Prerit您还希望它做什么?这是链接名称空间正常工作的方式。考虑一下$ PATH在bash中如何工作。在路径上删除可执行文件并不排除另一个具有相同名称的可执行文件在更上游。
Raymond Hettinger

2
@Raymond Hettinger我同意,只是增加了警告。大多数人可能对此一无所知。:D
Slayer杀手

@Prerit您可以强制dict避免这种情况,即:dict(ChainMap({}, y, x))
wjandrea19年

112

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

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

87

递归/深度更新字典

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

示范:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

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

print deepupdate(pluto_original, pluto_update)

输出:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

感谢rednaw的编辑。


1
这不能回答问题。这个问题显然要求原始字典x和y新建一个字典z,用y中的值替换x的值-而不是更新的字典。该答案通过将x中的值相加来修改y。更糟糕的是,它不会复制这些值,因此可以进一步修改已修改的字典y,并且修改内容可能会反映在字典x中。@Jérôme我希望这段代码不会对您的应用程序造成任何错误-至少考虑使用Deepcopy复制值。
亚伦·霍尔

1
@AaronHall同意这不能回答问题。但这满足了我的需求。我了解这些限制,但就我而言,这不是问题。考虑到这一点,该名称可能会引起误解,因为它可能会引起它没有提供的深层复制。但是它解决了深层嵌套。这是Martellibot的另一个实现:stackoverflow.com/questions/3232943/…
–Jérôme,

71

我不使用副本时可能想到的最佳版本是:

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

至少在CPython上,它比快,dict(x.items() + y.items())但没有快n = copy(a); n.update(b)。如果更改iteritems()items(),则此版本在Python 3中也可以使用,这是2to3工具自动完成的。

我个人最喜欢这个版本,因为它用一种功能语法很好地描述了我想要的内容。唯一的小问题是,来自y的值优先于来自x的值并不能完全清楚,但是我不认为很难弄清楚。


70

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

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

甚至

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

在Python 3.9中,您还可以使用| 和| =以及下面来自PEP 584的示例

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

用哪种方法比dict(x, **y)-solution 更好?正如您(@CarlMeyer)在您自己的答案说明(stackoverflow.com/a/39858/2798610)中提到的那样,Guido认为该解决方案是非法的
Blackeagle52

14
Guido不喜欢dict(x, **y)(非常好)原因,因为它y仅依赖具有有效关键字参数名称的键(除非您使用的是dict构造函数作弊的CPython 2.7)。此异议/限制不适用于PEP 448,后者将**拆包语法概括为dict文字。因此,该解决方案与相同dict(x, **y),没有缺点。
卡尔·梅耶

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

对于两个字典中都有键的项目,您可以通过将最后一个放在输出中来控制哪一个在输出中结束。


在python 3中,您将得到TypeError:+不支持的操作数类型:'dict_items'和'dict_items'...您应该使用list()封装每个dict,例如:dict(list(x.items())+ list (y.items()))
justSaid

49

虽然已经多次回答了该问题,但尚未列出此问题的简单解决方案。

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

它与上面提到的z0和邪恶z2一样快,但易于理解和更改。


3
但是它是三个语句,而不是一个表达式
fortran,

14
是! 提到的一种表达方式要么很慢,要么就是邪恶。好的代码是可读和可维护的。所以问题是问题而不是答案。我们应该寻求问题的最佳解决方案,而不是一站式解决方案。
phobie 2011年

7
失去the z4 = {}并将下一行更改为z4 = x.copy()-比仅好的代码不会做不必要的事情要好(这使其更具可读性和可维护性)。
martineau 2013年

3
您的建议会将其更改为Matthews的答案。尽管他的回答很好,但我认为我的可读性更高且更易于维护。多余的行只有在花费执行时间的情况下才是不好的。
phobie,

47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

在这些阴暗而可疑的答案中,这个出色的例子是在Python中合并字典的唯一且唯一的好方法,这是独裁者终身支持的Guido van Rossum本人!有人提出了一半的建议,但没有将其放在函数中。

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

给出:

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

39

如果您认为lambda是邪恶的,那么请继续阅读。根据要求,您可以使用一个表达式编写快速而高效的内存解决方案:

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}

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


33

是pythonic。使用理解

z={i:d[i] for d in [x,y] for i in d}

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

1
功能:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight

1
通过直接迭代键/值对来保存查找:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger

30

在python3中,该items方法不再返回list,而是返回一个view,其作用类似于set。在这种情况下,您将需要使用set联合,因为与的连接+将不起作用:

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

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

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

无论如何,我还是更喜欢这种表示法,因为将其视为固定的联合运算而不是串联(如标题所示)似乎更为自然。

编辑:

对于python 3还有几点。首先,请注意,dict(x, **y)除非输入的键y是字符串,否则该技巧在python 3中将不起作用。

而且,Raymond Hettinger的Chainmap 答案非常优雅,因为它可以将任意数量的dicts作为参数,但是从文档中看,它似乎依次遍历了每次查找的所有dicts列表:

查找顺序搜索基础映射,直到找到密钥为止。

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

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的粉丝,但在可能有很多查找的地方看起来不太实用。


22

滥用导致马修回答的单一表达解决方案:

>>> 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的一个表达式的限制。随时畏缩。

如果您不关心复制它,当然也可以这样做:

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

22

使用itertools的简单解决方案,该命令可保留顺序(后格优先)

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

它的用法是:

>>> 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}


16

即使答案对于此浅表字典而言是好的,但此处定义的方法均未进行深字典合并。

示例如下:

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

人们会期望这样的结果:

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

相反,我们得到以下信息:

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

如果“ one”条目确实是合并的,则其字典中的项目应具有“ depth_2”和“ extra”作为条目。

也使用链,不起作用:

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

结果是:

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

rcwesick进行的深度合并也产生相同的结果。

是的,可以合并示例字典,但是它们都不是合并的通用机制。一旦编写了可以真正合并的方法,我将在以后进行更新。


11

(仅适用于Python2.7 *;对于Python3 *有更简单的解决方案。)

如果您不反对导入标准库模块,则可以执行

from functools import reduce

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

(由于总是返回成功,所以中的or alambda是必需的。)dict.updateNone


11

如果您不介意变异x

x.update(y) or x

简单,可读,高效。您知道 update()总是会返回None,这是一个错误的值。因此,上述表达式x在更新后将始终等于。

标准库中的变异方法(如.update()None按约定返回,因此该模式也适用于这些方法。如果您使用的方法不遵循此约定,则or可能无法正常工作。但是,您可以使用元组显示和索引来使其成为单个表达式。无论第一个元素的计算结果如何,此方法都有效。

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

如果还没有x变量,则可以使用lambda本地变量而不使用赋值语句。这相当于lambda用作let表达式,这是功能语言中的一种常用技术,但可能不是Python语言。

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

尽管与下面使用新的walrus运算符(仅适用于Python 3.8+)没有什么不同:

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

如果您确实想要复制,则PEP 448样式最简单{**x, **y}。但是,如果您的(旧)Python版本中没有该功能,则let模式也可以在这里使用。

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

(当然,这等效于(z := x.copy()).update(y) or z,但是如果您的Python版本足够新,则可以使用PEP 448样式。)


10

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

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

用法(在python 3中测试):

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})

您可以改用lambda。


10

迄今为止,我列出的解决方案存在的问题是,在合并的词典中,键“ b”的值是10,但按照我的想法,应该是12。鉴于此,我提出以下内容:

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

结果:

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
您可能感兴趣的cytoolz.merge_withtoolz.readthedocs.io/en/latest/...
BLI

10

真傻,.update什么也没返回。
我只是使用一个简单的辅助函数来解决问题:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

例子:

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

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

这应该可以解决您的问题。


9

这可以通过单个dict理解来完成:

>>> 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)
    }

在我看来,“单个表达式”部分的最佳答案是因为不需要额外的功能,而且它很简短。


我怀疑性能不会很好。从每个字典中创建一个集合,然后仅遍历键意味着每次都需要对该值进行另一次查找(尽管相对较快,但仍会增加缩放函数的顺序)
Breezer

2
这完全取决于我们使用的python版本。在3.5及更高版本中,{** x,** y}给出了级联字典
Rashid Mv

9

由于PEP 572:Assignment Expressions,Python 3.8发行版(计划于2019年10月20日)将提供一个新选项。新的赋值表达式运算符使您可以分配的结果,并仍然使用它来调用,从而使组合的代码成为单个表达式,而不是两个语句,从而进行了更改::=copyupdate

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

至:

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

同时在各个方面都表现相同。如果还必须返回结果dict(您要求返回的表达式dict;上面创建并分配给newdict,但没有返回,因此您不能使用它将参数直接传递给函数la myfunc((newdict := dict1.copy()).update(dict2))) ,然后将其添加or newdict到末尾(因为updatereturns None是虚假的,因此它将求值并newdict作为表达式的结果返回):

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

重要警告:通常,我不建议采用以下方法:

newdict = {**dict1, **dict2}

拆包方法更清晰(对于一开始就知道要进行广义拆包的人来说,应该这样),根本不需要名称(因此,构造一个立即传递给a的临时文件时,它会更加简洁。函数或包含在list/ tuple文字等中),并且几乎肯定也更快,因为(在CPython上)大致等同于:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

但使用具体的dictAPI 在C层完成,因此不涉及动态方法查找/绑定或函数调用分派开销(在此(newdict := dict1.copy()).update(dict2)情况下,行为不可避免地与原始的两层相同,在不连续的步骤中执行工作,并进行动态查找/绑定/方法的调用。

它也更可扩展,因为合并三个dicts是显而易见的:

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

使用赋值表达式不会像这样缩放的地方;您能得到的最接近的是:

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

或没有Nones 的临时元组,但对每个None结果进行真实性测试:

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

其中的任一个是明显更恶心,并且包括进一步的低效(或者是临时浪费tupleNoneS表示逗号分离,或每个的无意义感实性测试updateNone用于返回or分离)。

赋值表达式方法的唯一真正优势在于:

  1. 您有需要同时处理sets和dicts的通用代码(它们都支持copyupdate,因此代码可以按您期望的那样大致工作)
  2. 您期望接收任意类似dict的对象,而不仅仅是dict自身,并且必须保留左侧的类型和语义(而不是以简单的结尾dict)。尽管myspecialdict({**speciala, **specialb})可能会起作用,但它会涉及一个额外的临时操作dict,并且如果myspecialdict具有平原dict无法保留的功能(例如,常规dicts现在基于键的首次出现保留顺序,而基于键的最后出现保留值;您可能想要一个根据最后一个保留订单键的外观,因此更新值也会将其移到末尾),那么语义将是错误的。由于赋值表达式版本使用命名方法(可能会重载以使其正常运行),因此它根本不会创建一个dict(除非dict1已经是一个dict),并保留原始类型(和原始类型的语义),同时避免任何临时性。

8
>>> 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}

此方法x用其副本覆盖。如果x是函数参数,则将不起作用(请参见示例
bartolo-otrit
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.