如何在Python中在单行中合并两个字典?
我想把两个字典合并成一个新的字典。
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)
>>> z
{'a': 1, 'b': 3, 'c': 4}
如果在两个字典中都有一个键 k
,那么只保留字典中第二个的值 y[k]
。
43 个回答
另一种选择:
z = x.copy()
z.update(y)
在你的情况下,你可以这样做:
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 | y
>>> z
{'a': 1, 'c': 11, 'b': 10}
如何在一行代码中合并两个Python字典?
对于字典 x
和 y
,它们合并后的字典 z
会用 y
的值替换 x
的值。
在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}
为此,Python 3.5引入了一种新的语法,提案在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的发布计划中已经实现,并且已经出现在Python 3.5的新特性文档中。
不过,由于许多组织仍在使用Python 2,你可能希望以向后兼容的方式来实现。传统的Python方式,在Python 2和Python 3.0-3.4中,可以通过两步来完成:
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中都能工作,适用于所有字典。例如,给定字典 a
到 g
:
z = merge_dicts(a, b, c, d, e, f, g)
在这个例子中,g
中的键值对会优先于字典 a
到 f
,依此类推。
对其他答案的批评
不要使用你在以前的接受答案中看到的内容:
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中,取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
构造函数,速度非常快且内存高效(甚至比我们的两步过程稍微更高效),但除非你确切知道这里发生了什么(即第二个字典作为关键字参数传递给字典构造函数),否则它很难阅读,这不是预期的用法,因此不符合Python的风格。
这里有一个用法的例子,已经在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
在邮件列表中,Python的创建者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)
符合字典规范,顺便说一下,这在Python 2和3中都有效。这个方法只对字符串键有效是关键字参数工作方式的直接结果,而不是字典的缺陷。在这个地方使用**运算符并不是对机制的滥用,实际上,**就是为了将字典作为关键字参数传递而设计的。
再次强调,当键不是字符串时,它在Python 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: {}}}
考虑到其他值类型的应急方案远远超出了这个问题的范围,所以我会指向我对“字典的字典合并”这一经典问题的回答。
性能较低但正确的临时解决方案
这些方法性能较低,但会提供正确的行为。它们的性能会比copy
和update
或新的解包方法低得多,因为它们在更高的抽象层次上遍历每个键值对,但它们确实遵循优先级顺序(后面的字典优先)。
你还可以在字典推导式中手动链接字典:
{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
关于字典的资源
- 我对Python字典实现的解释,已更新至3.6。
- 关于如何向字典添加新键的回答
- 将两个列表映射到字典中的回答
- 官方Python文档中的字典
- 字典甚至更强大 - Brandon Rhodes在Pycon 2017的演讲
- 现代Python字典,伟大思想的汇聚 - Raymond Hettinger在Pycon 2017的演讲