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

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

提问人:Carl Meyer 提问时间:9/2/2008 最后编辑:Muhammad AsadullahCarl Meyer 更新时间:10/15/2023 访问量:3205395

问:

我想将两个词典合并到一个新词典中。

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

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

每当两个字典中都存在一个键时,只应保留该值。ky[k]

python 字典 合并

评论


答:

98赞 Greg Hewgill 9/2/2008 #1
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

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

评论

27赞 justSaid 4/26/2019
在 python 3 中,你会得到 TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items' ...您应该使用 list() 封装每个字典,例如:dict(list(x.items()) + list(y.items()))
2赞 Cristian Ciupitu 1/8/2022
@justSaid itertools.chain(x.items(), y.items()) 也可以使用。
1813赞 Thomas Vander Stichele 9/2/2008 #2

就您而言,您可以执行以下操作:

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

这将按照您的意愿将最终的字典放入 ,并使 key 的值被第二个 () 字典的值正确覆盖:zby

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

如果您使用 Python 2,您甚至可以删除调用。要创建 z:list()

>>> z = dict(x.items() + 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
>>> z
{'a': 1, 'c': 11, 'b': 10}

评论

19赞 Gringo Suave 3/12/2020
不要使用它,因为它效率非常低。(请参阅下面的 timeit 结果。如果包装器函数不是一种选择,那么在 Py2 时代可能是必要的,但那些日子现在已经过去了。
0赞 mLstudent33 3/16/2023
这没有用。我在组合字典中得到了键作为值。
750赞 Matthew Schinckel 9/2/2008 #3

另一种选择:

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

评论

106赞 Alexander Oh 3/21/2013
为了澄清为什么这不符合问题提供的标准:它不是一个单一的表达式,它不返回 z。
22赞 eric 10/19/2017
这样说吧:如果你需要放两行注释,向你把代码交给的人解释你的一行代码......你真的在一行内做到了吗?:)我完全同意 Python 不适合这个:应该有一种更简单的方法。虽然这个答案更像python,但它真的那么明确或清晰吗? 不是人们经常使用的“核心”功能之一。Update
15赞 towr 2/24/2020
好吧,如果人们坚持让它成为单行本,你总是可以做到:P(lambda z: z.update(y) or z)(x.copy())
0赞 William Martens 7/15/2022
@AlexanderOh我不确定这是否是一个笑话;我认为这是一个完美(有效)的 answ!(至少就其工作而言)但是当然;是的;第二条评论开创了先例!无论哪种方式;它是Pythonic!Indeed
7赞 Alexander Oh 7/15/2022
@WilliamMartens这不是开玩笑。但让我们面对现实吧,如果你针对单行表达式进行优化,你就是在针对错误的事情进行优化。
441赞 Carl Meyer 9/2/2008 #4

另一个更简洁的选项:

z = dict(x, **y)

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

评论

3赞 amcgregor 4/12/2019
在 Python 3、PyPy 和 PyPy 3 中工作正常,无法与 Jython 或 Iron 对话。鉴于此模式已明确记录(请参阅本文档中的第三个构造函数表单),我认为这不是“实现细节”,而是有意使用的功能。
19赞 Carl Meyer 5/11/2019
@amcgregor 您错过了关键短语“如果 y 有任何非字符串键”。这就是在 Python3 中不起作用的东西;它在 CPython 2 中工作的事实是一个不可靠的实现细节。IFF 保证您的所有键都是字符串,这是一个完全支持的选项。
155赞 rcreswick 9/5/2008 #5

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

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

评论

0赞 Corentor 3/5/2021
当不希望使用较短和更简单的解决方案(用第二个字典替换公共键的值)的默认行为时,这是方便的解决方案。对于 Python 3,iteritems() 在字典中不再可用,可以简单地使用 items() 代替。
258赞 Tony Meyer 9/8/2008 #6

这可能不是一个流行的答案,但你几乎可以肯定不想这样做。如果您想要合并的副本,请使用复制(或深度复制,具体取决于您想要的内容),然后更新。这两行代码比使用 .items() + .items() 创建的单行代码更具可读性 - 更 Pythonic。显式比隐式好。

此外,当您使用 .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() 将在旧版本中起作用。

195赞 zaphod 10/23/2008 #7

在后续回答中,您询问了这两个备选方案的相对性能:

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

至少在我的机器上(运行 Python 2.5.2 的相当普通的x86_64),替代方案不仅更短、更简单,而且速度也快得多。您可以使用 Python 附带的模块自行验证这一点。z2timeit

示例 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 倍左右的优势获胜。不同的词典似乎会产生完全不同的结果,但似乎总是领先一步。(如果同一测试的结果不一致,请尝试使用大于默认 3 的数字传入。z2-r

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

使我得出结论,它比 快一些,但远不如 快。绝对不值得所有额外的打字。z3z1z2

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

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

换言之,它们似乎具有基本相同的性能。你认为这可能是巧合吗?我没有。。。。z0z2

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

你也可以把它写成

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

就像托尼所做的那样,但(毫不奇怪)符号的差异对性能没有任何可衡量的影响。使用适合您的。当然,他指出两句话的版本更容易理解,这是绝对正确的。

评论

9赞 Antti Haapala -- Слава Україні 3/16/2015
这在 Python 3 中不起作用; 不是 catenable,也不存在。items()iteritems
96赞 driax 10/15/2010 #8

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

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

它比 快,但不如 快,至少在 CPython 上是这样。如果您更改为 ,此版本也适用于 Python 3,这是由 2to3 工具自动完成的。dict(x.items() + y.items())n = copy(a); n.update(b)iteritems()items()

就我个人而言,我最喜欢这个版本,因为它在单个函数语法中很好地描述了我想要的东西。唯一的小问题是,y 的值优先于 x 的值并不完全明显,但我认为这并不难弄清楚。

73赞 phobie 10/15/2011 #9

虽然这个问题已经回答了好几次, 尚未列出该问题的简单解决方案。

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

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

评论

3赞 fortran 10/18/2011
但这是三个陈述,而不是一个表达
17赞 phobie 10/28/2011
是的!上面提到的一句话解决方案要么是缓慢的,要么是邪恶的。好的代码是可读的和维护的。所以问题是问题,而不是答案。我们应该寻求问题的最佳解决方案,而不是单线解决方案。
11赞 martineau 3/8/2013
删除并将下一行更改为 -- 比好的代码不做不必要的事情要好(这使得它更具可读性和可维护性)。z4 = {}z4 = x.copy()
3赞 phobie 5/6/2013
你的建议会把它改成马修斯的答案。虽然他的回答很好,但我认为我的答案更具可读性和更好的维护性。额外的行只有在花费执行时间时才会很糟糕。
0赞 Corman 6/15/2020
我建议你把它放到一个函数中
59赞 EMS 11/24/2011 #10

如果您认为 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}

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

126赞 Stan 11/29/2011 #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

示范:

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 的编辑。

评论

2赞 Russia Must Remove Putin 11/9/2018
这并不能回答这个问题。这个问题显然要求从原始词典 x 和 y 中创建一个新词典 z,其中 y 的值取代了 x 的值 - 而不是更新的词典。此答案通过添加 x 中的值来就地修改 y。更糟糕的是,它不会复制这些值,因此可以进一步修改修改后的字典 y,并且修改可以反映在字典 x 中。 @Jérôme 我希望这段代码不会给您的应用程序带来任何错误 - 至少考虑使用 deepcopy 来复制这些值。
2赞 Jérôme 11/9/2018
@AaronHall同意这并不能回答这个问题。但它满足了我的需求。我理解这些限制,但这在我的情况下不是问题。想想看,也许这个名字具有误导性,因为它可能会唤起它没有提供的深度副本。但它解决了深度嵌套问题。这是 Martellibot 的另一个实现:stackoverflow.com/questions/3232943/...
67赞 Sam Watkins 8/6/2012 #12
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'}
37赞 Mathieu Larose 10/17/2012 #13

两本词典

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

n 词典

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/

191赞 Raymond Hettinger 4/28/2013 #14

在 Python 3.0 及更高版本中,可以使用集合。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 = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}

Python 3.9 及更高版本的更新:您可以使用 PEP 584 联合运算符:

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

评论

6赞 Slayer 2/14/2017
但是在使用 ChainMap 时应该谨慎,有一个问题,如果你有重复的键,第一个映射的值就会被使用,当你调用一个 on 时,比如说 ChainMap c 将删除该键的第一个映射。del
12赞 Raymond Hettinger 2/15/2017
@Prerit 你还希望它能做什么?这是链式命名空间的正常工作方式。考虑一下 $PATH 在 bash 中是如何工作的。删除路径上的可执行文件并不排除在上游使用另一个同名的可执行文件。
4赞 Slayer 2/15/2017
@Raymond Hettinger,我同意,只是补充了一个警告。大多数人可能不知道它。:D
0赞 wjandrea 7/16/2019
@Prerit 你可以投射到避免这种情况,即:dictdict(ChainMap({}, y, x))
0赞 run_the_race 5/30/2022
@RaymondHettinger 我们都知道你以最合理的方式设计,谢谢你!ChainMap
6赞 kiriloff 5/27/2013 #15

使用字典推导,您可以

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

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

请注意 in comprehension 的语法if else

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

评论

10赞 wim 2/19/2014
我喜欢使用字典推导的想法,但您的实现很弱。使用而不是只是.... in list(y.keys())... in y
20赞 Bijou Trouvaille 7/19/2013 #16

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

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。

31赞 Claudiu 8/8/2013 #17

滥用导致了 Matthew 回答的单表达式解决方案:

>>> 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}
46赞 beardc 10/10/2013 #18

在 python3 中,该方法不再返回列表,而是返回一个视图,其作用类似于集合。在这种情况下,您需要采用集合并集,因为连接 将不起作用:items+

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

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

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

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

编辑:

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

此外,Raymond Hettinger 的 Chainmap 答案非常优雅,因为它可以接受任意数量的字典作为参数,但从文档来看,它似乎按顺序查看了每次查找的所有字典列表:

查找将依次搜索基础映射,直到找到键。

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

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

12赞 John La Rooy 11/13/2013 #19
>>> 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}

评论

0赞 bartolo-otrit 2/22/2019
此方法将覆盖其副本。如果是函数参数,这将不起作用(参见示例xx)
17赞 upandacross 12/4/2013 #20

迄今为止,我对列出的解决方案的问题是,在合并的字典中,键“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赞 bli 2/16/2017
你可能对以下内容感兴趣 (toolz.readthedocs.io/en/latest/...cytoolz.merge_with)
18赞 GetFree 3/2/2014 #21

太傻了,什么也没回来。
我只是使用一个简单的辅助函数来解决问题:
.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
5赞 bassounds 9/30/2014 #22

OP 的两个词典的合并是这样的:

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

具体来说,两个实体(和)的并集包含和/或的所有元素。 不幸的是,尽管帖子的标题如此,但 OP 要求的不是工会。xyxy

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

从 OP 的示例中:

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

是否可以更改列表,但如果字典包含列表(和嵌套列表)作为任一字典中的值,则上述方法将起作用。

评论

0赞 Carl Meyer 9/30/2014
为了清楚起见,我已经编辑了问题,不使用这个词。union
1赞 Alfe 5/19/2016
也许你的意思是......?{'a': 1, 'b': (2, 10), 'c': 11}
8996赞 Russia Must Remove Putin 11/11/2014 #23

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

对于字典 和 ,其浅层合并字典从 中获取值,替换 中的值。xyzyx

  • 在 Python 3.9.0 或更高版本(2020 年 10 月 17 日发布,PEP-584在此处讨论):

    z = x | y
    
  • 在 Python 3.5 或更高版本中:

    z = {**x, **y}
    
  • 在 Python 2(或 3.4 或更低版本)中,编写一个函数:

    def merge_two_dicts(x, y):
        z = x.copy()   # start with keys and values of x
        z.update(y)    # modifies z with keys and values of y
        return z
    

    现在:

    z = merge_two_dicts(x, y)
    

解释

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

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

期望的结果是得到一个新字典 (),其中合并了值,第二个字典的值覆盖了第一个字典的值。z

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

在这两种方法中,will 排在第二位,其值将取代 的值,因此将指向我们的最终结果。yxb3

尚未在 Python 3.5 上,但想要一个表达式

如果您还没有使用 Python 3.5 或需要编写向后兼容的代码,并且希望在单个表达式中实现此代码,那么性能最高的正确方法是将其放在函数中:

def merge_two_dicts(x, y):
    """Given two dictionaries, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

然后你有一个表达式:

z = merge_two_dicts(x, y)

您还可以创建一个函数来合并任意数量的字典,从零到非常大的数字:

def merge_dicts(*dict_args):
    """
    Given any number of dictionaries, shallow copy and merge into a new dict,
    precedence goes to key-value pairs in latter dictionaries.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

此函数将在 Python 2 和 3 中适用于所有字典。例如,将词典提供给:ag

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

中的键值对将优先于 字典,依此类推。gaf

对其他答案的批评

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

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

同样,当值是不可哈希的对象(例如列表)时,在 Python 3(在 Python 2.7 中)中获取 的并集也会失败。即使您的值是可哈希的,由于集合在语义上是无序的,因此在优先级方面的行为是未定义的。所以不要这样做:items()viewitems()

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

下面是一个示例,其中应该具有优先级,但由于集合的任意顺序,因此保留了 from 的值:yx

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

另一个你不应该使用的技巧:

z = dict(x, **y)

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

下面是 django 中修复的用法示例。

字典旨在采用可哈希键(例如 s 或元组),但当键不是字符串时,此方法在 Python 3 中失败。frozenset

>>> 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 规范,顺便说一句。适用于 Python 2 和 3。事实上,这只适用于字符串键,这是关键字参数工作方式的直接结果,而不是 dict 的缺点。在这个地方使用 ** 运算符也不是滥用该机制,事实上,** 被设计为将字典作为关键字传递。dict(x, **y)

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

>>> 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 最具可读性的解决方案。可读性很重要。

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

{**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: {}}}

提出其他值类型的偶然性远远超出了这个问题的范围,所以我将向您指出我对“字典的字典合并”的规范问题的答案

性能较低但正确的 Ad-hocs

这些方法的性能较低,但它们将提供正确的行为。 它们的性能将 和/或新的解包低得多,因为它们在更高的抽象级别遍历每个键值对,但它们确实遵循优先级顺序(后面的字典具有优先级)copyupdate

您还可以在字典推导式中手动链接词典:

{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()) # iteritems in Python 2

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

from itertools import chain
z = dict(chain(x.items(), y.items())) # iteritems in Python 2

性能分析

我只将对已知行为正确的用法进行性能分析。(独立,因此您可以自行复制和粘贴。

from timeit import repeat
from itertools import chain

x = dict.fromkeys('abcdefg')
y = dict.fromkeys('efghijk')

def merge_two_dicts(x, y):
    z = x.copy()
    z.update(y)
    return z

min(repeat(lambda: {**x, **y}))
min(repeat(lambda: merge_two_dicts(x, y)))
min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
min(repeat(lambda: dict(chain(x.items(), y.items()))))
min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))

在 Python 3.8.1 中,NixOS:

>>> min(repeat(lambda: {**x, **y}))
1.0804965235292912
>>> min(repeat(lambda: merge_two_dicts(x, y)))
1.636518670246005
>>> min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
3.1779992282390594
>>> min(repeat(lambda: dict(chain(x.items(), y.items()))))
2.740647904574871
>>> min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))
4.266070580109954
$ uname -a
Linux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux

词典资源

评论

1赞 Mohammad Azim 5/16/2019
仅字符串对关键字扩展的限制足以排除方法。但是,可以通过转换为 like 来使该方法可行。{**x, **y}itemsdictitemslistdict(list(x.items()), list(y.items()))
30赞 Russia Must Remove Putin 5/17/2019
@MohammadAzim“仅字符串”仅适用于可调用对象中的关键字参数扩展,不适用于通用解包语法。为了证明这有效:{**{(0, 1):2}} -> {(0, 1): 2}
17赞 Gringo Suave 3/13/2020
嗨,顶部是一个总结,是的。轮到你了。整件事将是一篇很棒的博客文章。 注意:Py 3.4 及以下版本是 EOL,3.5 在 2020-09 年接近 EOL。
25赞 Russia Must Remove Putin 5/17/2020
我同意人们渴望抛弃旧方式,但有时人们不得不在他们只有旧技术可用的环境中工作。人们还必须更新代码,在新方法旁边看到旧方法可以让他们自信地用等效的新代码替换旧代码。我愿意接受关于重新组织材料的建议,但我认为我们需要保留旧信息。
2赞 Vedran Šego 9/8/2020
哇!这是一个彻底的答案。不过,有一点小评论:“的预期用途是创建可读性的词典”。我认为这也是为了使代码不那么容易出错,因为会失败并显示“SyntaxError:关键字参数重复”,而不会(23 覆盖 17),并且您意外的双键不会被注意到。当然,如果你想允许双键(虽然我不明白为什么),那么语法是要走的路。当然,许多 linter 会警告双键。dict(**y)dict(a=17, b=19, a=23){"a": 17, "b": 19, "a": 23}"a"{...}
103赞 Bilal Syed Hussain 2/27/2015 #24

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

评论

0赞 Blackeagle52 3/4/2015
这个解决方案在哪些方面比 -solution 更好?正如你(@CarlMeyer)在你自己的答案(stackoverflow.com/a/39858/2798610)的注释中提到的,圭多认为该解决方案是非法的dict(x, **y)
18赞 Carl Meyer 3/5/2015
Guido 不喜欢它的原因(非常好),因为它只依赖于具有有效关键字参数名称的键(除非您使用的是 CPython 2.7,其中 dict 构造函数作弊)。此异议/限制不适用于 PEP 448,后者将解包语法推广到字典文字。因此,此解决方案具有与 相同的简洁性,而没有缺点。dict(x, **y)y**dict(x, **y)
14赞 RemcoGerlich 7/17/2015 #25

这可以通过单个字典推导来完成:

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

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

评论

0赞 Breezer 2/16/2017
不过,我怀疑性能不会很好;从每个字典中创建一个集合,然后只遍历键意味着每次都要对值进行另一次查找(虽然相对较快,但仍然会增加函数的缩放顺序)
3赞 Rashid Mv 12/23/2017
这完全取决于我们使用的 Python 版本。在 3.5 及更高版本中,{**x,**y} 给出了串联的字典
35赞 reubano 8/4/2015 #26

使用保留顺序的迭代工具的简单解决方案(后面的字典具有优先权)

# py2
from itertools import chain, imap
merge = lambda *args: dict(chain.from_iterable(imap(dict.iteritems, args)))

# py3
from itertools import chain
merge = lambda *args: dict(chain.from_iterable(map(dict.items, 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赞 reetesh11 11/30/2015 #27
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

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

评论

0赞 Eyong Kevin Enowanyo 8/8/2021
我建议使用 Counter 而不是 .这是因为,如果任何键的总和结果为 0,则 Counter 将删除它。.update()+
62赞 Robino 1/20/2016 #28

是 Pythonic。使用理解

z={k: v for d in [x,y] for k, v in d.items()}

>>> print z
{'a': 1, 'c': 11, 'b': 10}
19赞 kjo 3/28/2016 #29

(仅适用于 Python 2.7*;Python 3* 有更简单的解决方案。

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

from functools import reduce

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

(中的位是必需的,因为总是在成功时返回。or alambdadict.updateNone

9赞 Alfe 5/18/2016 #30

我知道这并不真正符合问题的具体细节(“一句话”),但由于上面的答案都没有朝这个方向发展,而很多答案都解决了性能问题,我觉得我应该贡献我的想法。

根据用例的不同,可能没有必要为给定的输入词典创建“真实”合并词典。在许多情况下,这样做的视图可能就足够了,即一个对象的行为类似于合并的字典,而无需完全计算它。可以这么说,合并词典的懒惰版本。

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

z = MergeDict(x, y)

使用这个新对象时,它的行为类似于合并的字典,但它将具有恒定的创建时间和恒定的内存占用,同时保持原始字典不变。创建它比提出的其他解决方案便宜得多。

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

如果您曾经觉得您更愿意进行真正的合并,那么调用会产生它(但当然比其他解决方案更昂贵,所以这值得一提)。dictdict(z)

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

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

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

评论

2赞 Alfe 5/19/2016
我现在看到一些答案指的是一个名为的类,该类仅在 Python 3 中可用,并且或多或少地执行我的代码所做的工作。因此,我为没有足够仔细地阅读所有内容而感到羞耻。但鉴于这只存在于 Python 3 中,请将我的回答作为对 Python 2 用户的贡献;-)ChainMap
5赞 clacke 7/28/2016
ChainMap 被向后移植到早期的 Python 中:pypi.python.org/pypi/chainmap
5赞 Mike Graham 11/18/2016 #31

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

评论

5赞 Jean-François Fabre 5/14/2019
当我们可以在原生 Python 中完成时,为什么还要使用第三方来执行如此微不足道的任务?
1赞 gboffi 5/30/2017 #32

这个问题被标记了,但考虑到这是一个相对较新的添加,并且投票最多、被接受的答案广泛涉及 Python 2.x 解决方案,我敢添加一个利用 Python 2.x 列表推导的令人讨厌的功能的一句话,即名称泄露......python-3x

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

我很高兴地说,上述内容在任何版本的 Python 3 上都不再有效。

25赞 gilch 9/22/2017 #33

如果你不介意变异,x

x.update(y) or x

简单、可读、高性能。您知道总是返回 ,这是一个 false 值。因此,上面的表达式在更新后将始终计算为 。update()Nonex

标准库中的大多数突变方法(如)都按照约定返回,因此这种模式也适用于这些方法。但是,如果您使用的是 dict 子类或其他一些不遵循此约定的方法,则可能会返回其左操作数,这可能不是您想要的。相反,您可以使用元组显示和索引,无论第一个元素的计算结果如何,它都可以工作(尽管它不那么漂亮):.update()Noneor

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

如果还没有变量,则可以在不使用赋值语句的情况下创建局部变量。这相当于用作 let 表达式,这是函数式语言中的一种常见技术,但可能是非 pythonic。xlambdalambda

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

虽然它与以下使用 new walrus 运算符(仅限 Python 3.8+)没有太大区别,

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

特别是当您使用默认参数时:

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

如果你确实想要一个副本,PEP 584 风格是 3.9+ 上最 Pythonic 的。如果您必须支持旧版本,PEP 448 样式对于 3.5+ 来说是最简单的。但是,如果这在您(甚至更旧的)Python 版本中不可用,那么 let 表达式模式在这里也有效。x | y{**x, **y}

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

(当然,这几乎等同于 ,但如果您的 Python 版本足够新,那么 PEP 448 样式将可用。(z := x.copy()).update(y) or z

5赞 litepresence 3/22/2018 #34

我很好奇我是否可以用一行字符串化方法击败公认的答案的时间:

我尝试了 5 种方法,之前没有提到过 - 所有一种衬里 - 都产生了正确的答案 - 我无法接近。

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

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 & 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')

结果:

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方法是从字典字符串返回字典的最快方法(尝试过的方法);比我认为是正常方法快得多(大约是 1/4 的时间)。我还了解到,应该不惜一切代价避免使用 YAML 方法。ast

是的,我知道这不是最好/正确的方法。我很好奇它是否更快,但事实并非如此;我发帖证明这一点。

评论

0赞 ShadowRanger 3/1/2019
请注意,该方法比 快,但也不全面。它不能处理不在 JSON 规范中的 Python 文字,所以没有 s、s、s、s(它可以处理 JSON 布尔,但不能直接字符串化 Python 布尔的结果)等。 速度较慢,但至少其中一些是处理更复杂输入的结果。也就是说,我很确定如果他们费心优化它可能会更快,评估 Python 文字字符串是代码中的瓶颈,这是非常罕见的。jsonast.literal_evaltuplesetfrozensetboolast.literal_eval
7赞 Josh Bode 4/16/2018 #35

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

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

注意:即使字典列表为空或仅包含一个元素,这也有效。

为了在 Python 3.9 或更高版本上进行更有效的合并,可以直接替换为:lambdaoperator.ior

>>> from functools import reduce
>>> from operator import ior
>>> l = [{'a': 1}, {'b': 2}, {'a': 100, 'c': 3}]
>>> reduce(ior, l, {})
{'a': 100, 'b': 2, 'c': 3}

对于 Python 3.8 或更低版本,可以使用以下内容作为替代:ior

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

评论

1赞 Ry- 3/19/2022
这是非常低效的(与时间成正比的键数平方一样糟糕)。请改用。operator.ior
0赞 Josh Bode 3/20/2022
谢谢 - 我已经更新了我的答案。我通常不太关心这里的效率,因为我会在初始化时使用这样的东西来合并一次配置(此外,我目前必须保持对 Python 3.7+ 的兼容性)
3赞 Tigran Saluev 5/11/2018 #36

我认为我丑陋的单行本在这里是必要的。

z = next(z.update(y) or z for z in [x.copy()])
# or
z = (lambda z: z.update(y) or z)(x.copy())
  1. 字典被合并。
  2. 单个表达式。
  3. 永远不敢使用它。

附言这是一个适用于两个 Python 版本的解决方案。我知道 Python 3 有这个东西,而且使用起来是正确的(如果你仍然有 Python 2,那么迁移到 Python 3 是正确的做法)。{**x, **y}

18赞 ShadowRanger 3/1/2019 #37

Python 3.8 发布时(计划于 2019 年 10 月 20 日发布)将有一个新选项,这要归功于 PEP 572:赋值表达式。新的赋值表达式运算符允许您赋值 的结果,并且仍然使用它来调用 ,使组合代码成为单个表达式,而不是两个语句,更改::=copyupdate

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

自:

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

同时在各个方面都表现得相同。如果你还必须返回结果(你要求一个返回的表达式;上面的表达式创建并赋值给 ,但没有返回它,所以你不能用它来将参数按原样传递给函数,a la ),那么只需添加到末尾(因为 returns ,这是虚假的,然后它将计算并返回作为表达式的结果):dictdictnewdictmyfunc((newdict := dict1.copy()).update(dict2))or newdictupdateNonenewdict

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

重要警告:一般来说,我不鼓励这种方法,而赞成:

newdict = {**dict1, **dict2}

解包方法更清晰(对于任何首先了解广义解包的人来说,你应该这样做),根本不需要结果的名称(因此在构造一个立即传递给函数或包含在 / 文字等中的临时时,它要简洁得多),并且几乎可以肯定也更快, (在 CPython 上)大致相当于:listtuple

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

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

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

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

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

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

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

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

这两者显然都更丑陋,并且包括进一步的低效率(要么是暂时浪费 S 用于逗号分隔,要么是对每个 ' 返回进行毫无意义的真实性测试以进行分离)。tupleNoneupdateNoneor

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

  1. 你有通用代码,需要同时处理 set s 和 dict s(它们都支持 和 ,所以代码的工作方式大致符合你的预期)copyupdate
  2. 你希望收到任意的类似字典的对象,而不仅仅是它自己,并且必须保留左侧的类型和语义(而不是以普通的结尾结束)。虽然可能有效,但它会涉及一个额外的临时,如果具有普通无法保留的功能(例如,常规 s 现在根据键的第一次出现保留顺序,而值基于键的最后一次出现;你可能想要一个根据键的最后一次出现保留顺序,因此更新值也会将其移动到末尾), 那么语义就错了。由于赋值表达式版本使用命名方法(这些方法可能被重载以适当运行),因此它根本不会创建一个(除非已经是 ),保留原始类型(和原始类型的语义),同时避免任何临时性。dictdictmyspecialdict({**speciala, **specialb})dictmyspecialdictdictdictdictdict1dict
11赞 Roushan 4/9/2020 #38

仅限 Python 3.9+

合并 (|) 和更新 (|=) 运算符已添加到内置类中。dict

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

增强分配版本就地运行:

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

参见 PEP 584

23赞 xjcl 6/2/2020 #39

Python 3.9 中的新功能使用联合运算符 () 合并 s,类似于 s:|dictset

>>> d = {'a': 1, 'b': 2}
>>> e = {'a': 9, 'c': 3}
>>> d | e
{'a': 9, 'b': 2, 'c': 3}

对于匹配的键,正确的字典优先

这也适用于修改就地:|=dict

>>> e |= d    # e = e | d
>>> e
{'a': 1, 'c': 3, 'b': 2}

评论

0赞 wim 6/23/2022
这增加了几个月前没有提到的内容吗?stackoverflow.com/a/61116810/674039
108赞 Nico Schlömer 7/10/2020 #40

我用perfplot对建议进行了基准测试,发现

x | y   # Python 3.9+

是与老好一起最快的解决方案

{**x, **y}

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

enter image description here


重现绘图的代码:

from collections import ChainMap
from itertools import chain
import perfplot


def setup(n):
    x = dict(zip(range(n), range(n)))
    y = dict(zip(range(n, 2 * n), range(n, 2 * n)))
    return x, y


def copy_update(x, y):
    temp = x.copy()
    temp.update(y)
    return temp


def add_items(x, y):
    return dict(list(x.items()) + list(y.items()))


def curly_star(x, y):
    return {**x, **y}


def chain_map(x, y):
    return dict(ChainMap({}, y, x))


def itertools_chain(x, y):
    return dict(chain(x.items(), y.items()))


def python39_concat(x, y):
    return x | y


b = perfplot.bench(
    setup=setup,
    kernels=[
        copy_update,
        add_items,
        curly_star,
        chain_map,
        itertools_chain,
        python39_concat,
    ],
    labels=[
        "copy_update",
        "dict(list(x.items()) + list(y.items()))",
        "{**x, **y}",
        "chain_map",
        "itertools.chain",
        "x | y",
    ],
    n_range=[2 ** k for k in range(18)],
    xlabel="len(x), len(y)",
    equality_check=None,
)
b.save("out.png")
b.show()

评论

0赞 Peter Mortensen 9/30/2022
可以添加测试条件吗?确切的 Python 版本。Python 实现(例如 CPython)。硬件(CPU 类型和型号、L2 缓存大小、时钟速度、动态时钟速度缩放、内核数等)。操作系统(类型、版本和版本)。任何其他可能相关的内容(例如,暖/冷)。(但是*******没有 But ******* “Edit:”、“Update:” 或类似内容 - 答案应该看起来像今天写的一样)
0赞 william_grisaitis 3/24/2023
最好有两个以上字典的例子,例如x_1 | x_2 | ... | x_n
10赞 disooqi 10/6/2020 #41

在 Python 3.9 中

基于 PEP 584,新版本的 Python 为字典引入了两个新的运算符:union (|) 和 in-place union (|=)。您可以使用 |合并两个字典,而 |= 将就地更新字典:

>>> pycon = {2016: "Portland", 2018: "Cleveland"}
>>> europython = {2017: "Rimini", 2018: "Edinburgh", 2019: "Basel"}

>>> pycon | europython
{2016: 'Portland', 2018: 'Edinburgh', 2017: 'Rimini', 2019: 'Basel'}

>>> pycon |= europython
>>> pycon
{2016: 'Portland', 2018: 'Edinburgh', 2017: 'Rimini', 2019: 'Basel'}

如果 d1 和 d2 是两个字典,则执行与 相同的操作。这 |运算符用于计算集合的并集,因此您可能已经熟悉了该符号。d1 | d2{**d1, **d2}

使用的一个优点是它适用于不同的类似字典的类型,并在合并过程中保留该类型:|

>>> from collections import defaultdict
>>> europe = defaultdict(lambda: "", {"Norway": "Oslo", "Spain": "Madrid"})
>>> africa = defaultdict(lambda: "", {"Egypt": "Cairo", "Zimbabwe": "Harare"})

>>> europe | africa
defaultdict(<function <lambda> at 0x7f0cb42a6700>,
  {'Norway': 'Oslo', 'Spain': 'Madrid', 'Egypt': 'Cairo', 'Zimbabwe': 'Harare'})

>>> {**europe, **africa}
{'Norway': 'Oslo', 'Spain': 'Madrid', 'Egypt': 'Cairo', 'Zimbabwe': 'Harare'}

当您想要有效地处理缺少的键时,可以使用 defaultdict。请注意,保留 defaultdict,而不保留。|{**europe, **africa}

字典的工作方式与列表的工作方式之间存在一些相似之处。事实上,运算符最初也被提议合并字典。当您查看就地运算符时,这种对应关系变得更加明显。|++

的基本用途是就地更新字典,类似于:|=.update()

>>> libraries = {
...     "collections": "Container datatypes",
...     "math": "Mathematical functions",
... }
>>> libraries |= {"zoneinfo": "IANA time zone support"}
>>> libraries
{'collections': 'Container datatypes', 'math': 'Mathematical functions',
 'zoneinfo': 'IANA time zone support'}

将词典与 合并时,两个词典都需要具有正确的词典类型。另一方面,就地运算符 () 乐于使用任何类似字典的数据结构:||=

>>> libraries |= [("graphlib", "Functionality for graph-like structures")]
>>> libraries
{'collections': 'Container datatypes', 'math': 'Mathematical functions',
 'zoneinfo': 'IANA time zone support',
 'graphlib': 'Functionality for graph-like structures'}

评论

1赞 wim 6/23/2022
其他答案已经涵盖了这些
4赞 Brian 8/23/2021 #42

一种方法是深度合并。在 3.9+ 中使用运算符,用于 dict 是一组默认设置,dict 是一组正在使用的现有设置。我的目标是合并任何添加的设置,而不覆盖 中的现有设置。我相信这种递归实现将允许人们使用来自另一个字典的新值升级字典。|newexistingnewexisting

def merge_dict_recursive(new: dict, existing: dict):
    merged = new | existing

    for k, v in merged.items():
        if isinstance(v, dict):
            if k not in existing:
                # The key is not in existing dict at all, so add entire value
                existing[k] = new[k]

            merged[k] = merge_dict_recursive(new[k], existing[k])
    return merged

测试数据示例:

new
{'dashboard': True,
 'depth': {'a': 1, 'b': 22222, 'c': {'d': {'e': 69}}},
 'intro': 'this is the dashboard',
 'newkey': False,
 'show_closed_sessions': False,
 'version': None,
 'visible_sessions_limit': 9999}
existing
{'dashboard': True,
 'depth': {'a': 5},
 'intro': 'this is the dashboard',
 'newkey': True,
 'show_closed_sessions': False,
 'version': '2021-08-22 12:00:30.531038+00:00'}
merged
{'dashboard': True,
 'depth': {'a': 5, 'b': 22222, 'c': {'d': {'e': 69}}},
 'intro': 'this is the dashboard',
 'newkey': True,
 'show_closed_sessions': False,
 'version': '2021-08-22 12:00:30.531038+00:00',
 'visible_sessions_limit': 9999}

评论

0赞 William Martens 7/15/2022
对于眼前的问题,这是一个完全有效的答案;在我看来;非常好!:)
2赞 worroc 9/26/2021 #43

字典深度合并:

from typing import List, Dict
from copy import deepcopy

def merge_dicts(*from_dicts: List[Dict], no_copy: bool=False) -> Dict :
    """ no recursion deep merge of two dicts

    By default creates fresh Dict and merges all to it.

    no_copy = True, will merge all dicts to a fist one in a list without copy.
    Why? Sometime I need to combine one dictionary from "layers".
    The "layers" are not in use and dropped immediately after merging.
    """

    if no_copy:
        xerox = lambda x:x
    else:
        xerox = deepcopy

    result = xerox(from_dicts[0])

    for _from in from_dicts[1:]:
        merge_queue = [(result, _from)]
        for _to, _from in merge_queue:
            for k, v in _from.items():
                if k in _to and isinstance(_to[k], dict) and isinstance(v, dict):
                    # key collision add both are dicts.
                    # add to merging queue
                    merge_queue.append((_to[k], v))
                    continue
                _to[k] = xerox(v)

    return result

用法:

print("=============================")
print("merge all dicts to first one without copy.")
a0 = {"a":{"b":1}}
a1 = {"a":{"c":{"d":4}}}
a2 = {"a":{"c":{"f":5}, "d": 6}}
print(f"a0 id[{id(a0)}] value:{a0}")
print(f"a1 id[{id(a1)}] value:{a1}")
print(f"a2 id[{id(a2)}] value:{a2}")
r = merge_dicts(a0, a1, a2, no_copy=True)
print(f"r  id[{id(r)}] value:{r}")

print("=============================")
print("create fresh copy of all")
a0 = {"a":{"b":1}}
a1 = {"a":{"c":{"d":4}}}
a2 = {"a":{"c":{"f":5}, "d": 6}}
print(f"a0 id[{id(a0)}] value:{a0}")
print(f"a1 id[{id(a1)}] value:{a1}")
print(f"a2 id[{id(a2)}] value:{a2}")
r = merge_dicts(a0, a1, a2)
print(f"r  id[{id(r)}] value:{r}")

评论

0赞 Peter Mortensen 9/30/2022
真的有必要将显式 for 循环嵌套在三级深度吗?