如何在一个表达式中合并两个 Python 字典?
对于字典 x 和 y,z 变为浅合并字典,其中 y 中的值替换 x 中的值。
-
在 Python 3.9.0 或更高版本(2020 年 10 月 17 日发布)中:PEP-584、discussed here 已实现并提供了最简单的方法:
z = x | y # NOTE: 3.9+ ONLY
-
在 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 和 available as of Python 3.5 中提出的一种新语法是
z = {**x, **y}
而且确实是一个表达式。
请注意,我们也可以使用文字表示法进行合并:
z = {**x, 'foo': 1, 'bar': 2, **y}
现在:
>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}
它现在显示为在 release schedule for 3.5, PEP 478 中实现,并且现在已进入 What's New in 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。例如给定字典 a 到 g:
z = merge_dicts(a, b, c, d, e, f, g)
g 中的键值对将优先于字典 a 到 f,依此类推。
其他答案的批评
不要使用您在以前接受的答案中看到的内容:
z = dict(x.items() + y.items())
在 Python 2 中,您在内存中为每个 dict 创建两个列表,在内存中创建长度等于前两个加在一起的长度的第三个列表,然后丢弃所有三个列表以创建 dict。 在 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 构造函数,速度非常快且内存效率很高(甚至比我们的两步过程略高),但除非您确切知道这里发生了什么(也就是说,第二个 dict 被传递为dict 构造函数的关键字参数),很难阅读,这不是预期的用途,因此它不是 Pythonic。
以下是remediated in django 的用法示例。
字典旨在采用可散列的键(例如frozensets 或元组),但当键不是字符串时,此方法在 Python 3 中失败。
>>> c = dict(a, **b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
来自mailing list,该语言的创建者 Guido van Rossum 写道:
我很好
宣布 dict({}, **{1:3}) 非法,因为毕竟它是滥用
**机制。
和
显然 dict(x, **y) 正在作为“呼叫”的“酷黑客”
x.update(y) 并返回 x"。我个人觉得它比
很酷。
我的理解(以及对creator of the language 的理解)dict(**y) 的预期用途是为了创建字典以提高可读性,例如:
dict(a=1, b=10, c=11)
而不是
{'a': 1, 'b': 10, 'c': 11}
对cmets的回应
尽管 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 中得到了修复,因为这种用法可能是一个重大变化。
我向您提出,故意编写仅适用于一种语言版本或仅在某些任意约束下有效的代码是恶意无能。
更多cmets:
dict(x.items() + y.items()) 仍然是 Python 2 最易读的解决方案。可读性很重要。
我的回复:merge_two_dicts(x, y) 实际上对我来说似乎更清楚,如果我们真的关心可读性的话。而且它不向前兼容,因为 Python 2 越来越被弃用。
{**x, **y} 似乎不处理嵌套字典。嵌套键的内容只是被覆盖,而不是合并[...]我最终被这些不递归合并的答案烧毁了,我很惊讶没有人提到它。在我对“合并”一词的解释中,这些答案描述了“将一个字典与另一个字典更新”,而不是合并。
是的。我必须让你回到这个问题,它要求 两个 字典的 shallow 合并,第一个的值被第二个的值覆盖- 在一个表达式中。
假设有两个字典,其中一个可能会递归地将它们合并到一个函数中,但是您应该注意不要从任一源修改字典,避免这种情况的最可靠方法是在分配值时进行复制。由于键必须是可散列的,因此通常是不可变的,因此复制它们是没有意义的:
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: {}}}
想出其他值类型的意外情况远远超出了这个问题的范围,所以我会指出你my answer to the canonical question on a "Dictionaries of dictionaries merge"。
这些方法的性能较差,但它们会提供正确的行为。
它们的性能将远低于 copy 和 update 或新的解包,因为它们在更高的抽象级别上迭代每个键值对,但它们确实 > 尊重优先顺序(后面的字典有优先级)
您也可以在 dict comprehension 中手动链接字典:
{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
字典资源