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

我想把两个字典合并成一个新的字典。

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

每当两个字典中都有键k时,只应保留值y[k]

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

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

在你的情况下,你可以这样做:

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

这将如您所愿,将最终的字典放在z中,并使键b的值被第二个(y)字典的值正确覆盖:

>>> 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,您甚至可以删除list()调用。要创建z:

>>> 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 | yprint(z)
{'a': 1, 'c': 11, 'b': 10}

另一种选择:

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

另一个更简洁的选项:

z = dict(x, **y)

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

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

def merge(d1, d2, merge_fn=lambda x,y:y):"""Merges two dictionaries, non-destructively, combiningvalues on duplicate keys as defined by the optional mergefunction.  The default behavior replaces the values in d1with corresponding values in d2.  (There is no other generallyapplicable merge strategy, but often you'll have homogeneoustypes in your dicts, so specifying a merge technique can bevaluable.)
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] = vreturn result

这可能不是一个受欢迎的答案,但你几乎肯定不想这样做。如果你想要一个合并的副本,那么使用复制(或深度复制,取决于你想要什么),然后更新。这两行代码比使用.项目()+.项目()的单行创建更具可读性-更Pythonic。显式比隐式好。

此外,当您使用.项目()(Python 3.0之前)时,您正在创建一个包含字典中项目的新列表。如果您的字典很大,那么这是相当大的开销(两个大列表将在创建合并的字典时被丢弃)。

关于时间

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

在我看来,前两个之间的微小减速对于易读性来说是值得的。此外,用于字典创建的关键字参数仅在Python 2.3中添加,而Copy()和date()将在旧版本中工作。

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

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

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

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

让我得出结论,z3z1快一些,但没有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

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

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

你也可以这样写

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

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

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

from itertools import chainx = {'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(),此版本也适用于Python 3,这是由2to3工具自动完成的。

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

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

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

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

如果你认为lambdas是邪恶的,那么不要再读了。根据要求,您可以使用一个表达式编写快速且节省内存的解决方案:

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}

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

递归/深度更新字典

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

def dict_merge(a, b):c = a.copy()c.update(b)return c
new = dict_merge(old, extras)

在这些阴暗和可疑的答案中,这个闪亮的例子是在Python中合并dicts的唯一好方法,得到了终身独裁者Guido van Rossum的认可!其他人建议了一半,但没有把它放在函数中。

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

提供:

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

两本字典

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/

在Python 3.0及更高版本中,您可以使用#0将多个dict或其他映射组合在一起以创建一个可更新的视图:

>>> 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 --> 1b --> 10c --> 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}

使用字典理解,您可以

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}

注意if else的语法

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

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

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代替。

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

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

在python3中,items方法不再返回列表,而是查看,它的行为就像一个集合。在这种情况下,你需要采用set联合,因为与+连接不起作用:

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

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

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

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

编辑:

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

此外,Raymond Hettinger的Chainmap回答非常优雅,因为它可以采用任意数量的dicts作为参数,但从文档看起来它像是按顺序查看每次查找的所有dicts列表:

查找连续搜索底层映射,直到找到一个键。

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

In [1]: from collections import ChainMapIn [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 loopIn [6]: timeit for k in chainmap_dict: chainmap_dict[k]10000 loops, best of 3: 27.1 µs per loop

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

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

我对迄今为止列出的解决方案的问题是,在合并的字典中,键“b”的值是10,但按照我的思维方式,它应该是12。在这种情况下,我提出以下建议:

import timeit
n=100000su = """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 togetherx = {'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}

.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

OP的两个字典的联合将是这样的:

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

具体来说,两个实体(xy)的并集包含x和/或y的所有元素。不幸的是,OP要求的不是工会,尽管职位的标题。

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

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

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

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

对于字典xy,它们的浅合并字典zy中获取值,替换来自x的值。

  • 在Python 3.9.0或更高版本(2020年10月17日发布,#0这里讨论)中:

    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 xz.update(y)    # modifies z with keys and values of yreturn 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

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

还没有在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)

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

对其他答案的批评

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

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

在Python 2中,您为每个字典在内存中创建两个列表,在内存中创建第三个列表,其长度等于前两个列表的长度,然后丢弃所有三个列表以创建字典。

>>> 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()的并集(在Python 2.7中为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'

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

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

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

z = dict(x, **y)

这使用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写道:

我很高兴宣布判决({}, **{1:3})非法,因为毕竟它是滥用**机制。

显然,“呼叫”的“酷黑客”(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)符合字典规范,btw。适用于Python 2和3。这只适用于字符串键的事实是关键字参数工作方式的直接结果,而不是字典的缺点。在这个地方使用**运算符也不是对机制的滥用,事实上,**就是为了将字典作为关键字传递而设计的。

同样,当键不是字符串时,它不适用于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: {}}}

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

性能较差但正确的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 chainz = dict(chain(x.items(), y.items())) # iteritems in Python 2

性能分析

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

from timeit import repeatfrom 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 -aLinux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux

字典上的资源

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

这可以通过一个单一的字典理解来完成:

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

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

简单的解决方案,使用迭代工具,保持秩序(后者有优先权)

# py2from itertools import chain, imapmerge = lambda *args: dict(chain.from_iterable(imap(dict.iteritems, args)))
# py3from itertools import chainmerge = 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}
from collections import Counterdict1 = {'a':1, 'b': 2}dict2 = {'b':10, 'c': 11}result = dict(Counter(dict1) + Counter(dict2))

这应该能解决你的问题。

使用Pythonic。使用理解

z={k: v for d in [x,y] for k, v in d.items()}
>>> print z{'a': 1, 'c': 11, 'b': 10}

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

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

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

我知道这并不符合问题的具体情况(“一行”),但是由于上面的答案中的没有指向了这个方向,而很多答案都解决了性能问题,我觉得我应该贡献我的想法。

根据用例,可能没有必要为给定的输入字典创建一个“真正的”合并字典。在许多情况下,这样做的查看可能就足够了,例如,一个行为喜欢的对象,合并字典将不完全计算它。可以说,合并字典的懒惰版本。

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

z = MergeDict(x, y)

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

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

如果你觉得你更喜欢有一个真正的合并dict,那么调用dict(z)会产生它(当然比其他解决方案成本更高,所以这只值得一提)。

您还可以使用此类来制作一种写时复制字典:

a = { 'x': 3, 'y': 4 }b = MergeDict(a)  # we merge just one dictb['x'] = 5print 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:passraise 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, vfound.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())

您可以使用#0

这个问题被标记为python-3x,但是,考虑到这是一个相对较新的添加,并且投票最多,接受的答案广泛涉及Python 2. x解决方案,我敢添加一个利用Python 2. x列表理解的烦人功能的衬垫,即名字泄露

$ python2Python 2.7.13 (default, Jan 19 2017, 14:48:08)[GCC 6.3.0 20170118] on linux2Type "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上都不起作用。

如果你不介意变异x

x.update(y) or x

简单、可读、高性能。您知道update()总是返回None,这是一个假值。所以上面的表达式在更新后总是计算为x

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

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

如果变量中还没有x,您可以使用lambda在不使用赋值语句的情况下创建本地。这相当于使用lambda作为让表达式,这是函数式语言中的常见技术,但可能不是pythonic。

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

虽然它与以下使用新的海象运算符(仅限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样式x | y是3.9+上最Pythonic的。如果你必须支持旧版本,PEP 448样式{**x, **y}在3.5+上最简单。但是如果你的(甚至更旧的)Python版本中不可用,让表达式模式在这里也可以。

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

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

我很好奇,如果我能击败接受答案的时间与一行stringify方法:

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

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

import jsonimport yamlimport timefrom ast import literal_eval as literal
def merge_two_dicts(x, y):z = x.copy()   # start with x's keys and valuesz.update(y)    # modifies z with y's keys and values & returns Nonereturn 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 yaml0.29134678840637207 {'c': 11, 'b': 10, 'a': 1} stringify literal0.2208399772644043 {'c': 11, 'b': 10, 'a': 1} stringify eval0.1106564998626709 {'c': 11, 'b': 10, 'a': 1} stringify replace0.07989692687988281 {'c': 11, 'b': 10, 'a': 1} stringify json0.005082368850708008 {'c': 11, 'b': 10, 'a': 1} accepted

我确实从中学到的是,JSON方法是(尝试过的)从字典字符串返回字典的最快方法;比我认为使用ast的正常方法快得多(大约1/4的时间)。

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

这是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或更高版本上更有效地合并,lambda可以直接替换为operator.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}

我认为我的丑陋的一句话在这里是必要的。

z = next(z.update(y) or z for z in [x.copy()])# orz = (lambda z: z.update(y) or z)(x.copy())
  1. 命令被合并。
  2. 单一表达。
  3. 从来不敢使用它。

P. S.这是一个适用于两个版本Python的解决方案。我知道Python 3有这个{**x, **y}的东西,它是正确的使用方式(如果你还有Python 2,那么迁移到Python 3是正确的做法)。

由于PEP 572:赋值表达式,Python 3.8发布(定于2019年10月20日)时会有一个新选项。新的赋值表达式运算符:=允许您赋值copy的结果并仍然使用它来调用update,将组合代码保留为单个表达式,而不是两个语句,更改:

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

到:

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

如果你还必须返回结果dict(你要求一个返回dict的表达式;上面创建并分配给newdict,但没有返回它,所以你不能用它来按原样将参数传递给函数,a lamyfunc((newdict := dict1.copy()).update(dict2))),然后只需在末尾添加or newdict(因为update返回None,这是false sy,然后它将评估并返回newdict作为表达式的结果):

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

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

newdict = {**dict1, **dict2}

解包方法更清晰(对于任何一开始就知道广义解包的人来说,你应该这么做),根本不需要结果的名称(因此在构造立即传递给函数或包含在list/tuple文字或类似内容中的临时时,它更简洁),并且几乎可以肯定更快,(在CPython上)大致相当于:

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

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

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

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

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

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

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

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

其中任何一个都明显丑陋得多,并且包括进一步的低效率(要么浪费None的临时tuple用于逗号分离,要么对每个updateNone返回进行无意义的真实性测试用于or分离)。

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

  1. 您有需要处理#0和#1的通用代码(它们都支持copyupdate,所以代码的工作方式大致与您期望的一样)
  2. dict0,而不仅仅是dict本身,dict1(而不是以普通的dict结束)。虽然myspecialdict({**speciala, **specialb})可能有效,但它会涉及一个额外的临时dict,如果myspecialdict具有普通dict无法保留的功能(例如,普通dict现在根据键的第一次出现保留顺序,而值基于键的最后一次出现;你可能希望基于键的dict2外观保留顺序,因此更新值也会将其移动到末尾),那么语义学就错了。由于赋值表达式版本使用命名方法(可能重载以适当地行为),它根本不会创建dict(除非dict1已经是dict),保留原始类型(和原始类型的语义学),同时避免任何临时变量。

仅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

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

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

对于匹配键,右#0优先

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

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

我用灌流图对建议进行了基准测试,发现好的旧

x | y

是最快的解决方案与良好的老

{**x, **y}

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

在此处输入图片描述


重现情节的代码:

from collections import ChainMapfrom itertools import chainimport 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()

在Python 3.9中

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

>>> 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 | africadefaultdict(<function <lambda> at 0x7f0cb42a6700>,{'Norway': 'Oslo', 'Spain': 'Madrid', 'Egypt': 'Cairo', 'Zimbabwe': 'Harare'})
>>> {**europe, **africa}{'Norway': 'Oslo', 'Spain': 'Madrid', 'Egypt': 'Cairo', 'Zimbabwe': 'Harare'}

当您想有效地处理丢失的键时,您可以使用默认值。请注意,|保留默认值,而{**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'}

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

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

DITS的深度合并:

from typing import List, Dictfrom 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:xelse: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 queuemerge_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}")