如何在 Python 中将两个字典合并到一个表达式中?
- 2024-11-15 08:36:00
- admin 原创
- 14
问题描述:
我想将两本词典合并为一本新词典。
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)
>>> z
{'a': 1, 'b': 3, 'c': 4}
k
每当两个字典中都存在一个键时,就只y[k]
应保留其值。
解决方案 1:
如何在单个表达式中合并两个 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}
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,您可能希望以向后兼容的方式执行此操作。经典的 Pythonic 方式(在 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()))
。这浪费了资源和计算能力。
类似地,当值是不可哈希的对象(例如列表)时,items()
在 Python 3 中(在 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
s 或元组),但是当键不是字符串时,此方法在 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: {}}}
为其他价值类型提出意外事件远远超出了这个问题的范围,所以我将向您指出我对“字典合并”这一典型问题的回答。
性能较差但正确的临时方案
这些方法性能较差,但它们会提供正确的行为。它们的性能会比和或新的解包低得多,因为它们在更高的抽象级别上迭代每个键值对,但它们确实尊重优先顺序(后者的字典优先)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 上的演讲
解决方案 2:
对于你的情况,你可以这样做:
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}
解决方案 3:
另一种选择:
z = x.copy()
z.update(y)
解决方案 4:
另一个更简洁的选择:
z = dict(x, **y)
注意:这已成为一个流行的答案,但需要指出的是,如果y
有任何非字符串键,那么这完全有效是对 CPython 实现细节的滥用,并且它在 Python 3、PyPy、IronPython 或 Jython 中不起作用。此外,Guido 不是粉丝。因此,我不推荐将这种技术用于向前兼容或交叉实现的可移植代码,这实际上意味着应该完全避免使用它。
解决方案 5:
这可能不会是一个受欢迎的答案,但你几乎肯定不想这样做。如果您想要一个合并的副本,那么使用 copy (或deepcopy,取决于您想要什么),然后更新。这两行代码比使用 .items() + .items() 的单行创建更具可读性 - 更具 Python 风格。显式优于隐式。
此外,当您使用 .items() (Python 3.0 之前)时,您将创建一个包含字典中项目的新列表。如果您的字典很大,那么这将带来相当大的开销(两个大列表将在创建合并字典后立即被丢弃)。update() 可以更有效地工作,因为它可以逐项运行第二个字典。
就时间而言:
>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))
y=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()
temp.update(y)", "x = dict(zip(range(1000), range(1000)))
y=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)))
y=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027
在我看来,前两者之间的微小减速对于可读性来说是值得的。此外,用于创建字典的关键字参数仅在 Python 2.3 中添加,而 copy() 和 update() 可以在旧版本中使用。
解决方案 6:
在后续的回答中,您询问了这两种替代方案的相对性能:
z1 = dict(x.items() + y.items())
z2 = dict(x, **y)
至少在我的计算机上(运行 Python 2.5.2 的相当普通的 x86_64),替代方案z2
不仅更短更简单,而且速度也快得多。您可以使用timeit
Python 附带的模块亲自验证这一点。
示例 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 个短字符串映射到整数以及将整数映射到 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 核心中。Pythondict
在很多地方都有使用;优化其操作是一件大事。
你也可以这样写
z0 = x.copy()
z0.update(y)
就像 Tony 所做的那样,但(毫不奇怪)符号的差异对性能没有任何可衡量的影响。使用任何你认为合适的符号。当然,他指出双语句版本更容易理解,这是完全正确的。
解决方案 7:
在 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 = {'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}
解决方案 8:
我想要一些类似的东西,但能够指定如何合并重复键上的值,所以我破解了这个(但没有对其进行大量测试)。显然这不是一个表达式,而是一个函数调用。
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
解决方案 9:
递归/深度更新字典
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 的编辑。
解决方案 10:
我使用perfplot对建议进行了基准测试,发现
x | y # Python 3.9+
是最快的解决方案,加上好的老
{**x, **y}
和
temp = x.copy()
temp.update(y)
重现情节的代码:
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()
解决方案 11:
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'}
解决方案 12:
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z
对于在两个字典中都有键的项目(“b”),您可以通过将其放在最后来控制哪一个最终出现在输出中。
解决方案 13:
我认为不使用复制的最佳版本是:
from itertools import chain
x = {'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()
为,此版本在 Python 3 中也可以使用items()
,这是由 2to3 工具自动完成的。
我个人最喜欢这个版本,因为它用单一函数语法很好地描述了我想要的东西。唯一的小问题是,它没有完全清楚地表明来自 y 的值优先于来自 x 的值,但我认为弄清楚这一点并不难。
解决方案 14:
虽然这个问题已经被回答过几次了,但是这个简单的解决方案还没有被列出。
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)
它和上面提到的z0和邪恶的z2一样快,但易于理解和改变。
解决方案 15:
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'}
解决方案 16:
变得 Pythonic。使用推导式:
z={k: v for d in [x,y] for k, v in d.items()}
>>> print z
{'a': 1, 'c': 11, 'b': 10}
解决方案 17:
如果你认为 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}
如上所述,使用两行或编写一个函数可能是更好的方法。
解决方案 18:
在 python3 中,该items
方法不再返回列表,而是返回视图,其作用类似于集合。在这种情况下,您需要采用集合并集,因为连接+
不起作用:
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答案非常优雅,因为它可以将任意数量的字典作为参数,但从文档来看,它似乎按顺序查看每次查找的所有字典列表:
查找依次搜索底层映射,直到找到一个键。
如果你的应用程序中有大量的查找,这可能会减慢你的速度:
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 的粉丝,但在可能进行多次查找的情况下,它看起来不太实用。
解决方案 19:
两本词典
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/
解决方案 20:
使用 itertools 的简单解决方案可以保留顺序(后者的字典优先)
# 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}
解决方案 21:
滥用导致马修的答案只有一个表达解决方案:
>>> 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:
如果你不介意变异的话x
,
x.update(y) or x
简单、易读、高效。您知道 update()
始终返回None
,这是一个假值。因此,在更新上述表达式后,其结果将始终为x
。
标准库中的大多数变异方法(如.update()
)None
都按惯例返回,因此这种模式也适用于它们。但是,如果您使用的是 dict 子类或其他不遵循此惯例的方法,则or
可能会返回其左操作数,这可能不是您想要的。相反,您可以使用元组显示和索引,无论第一个元素的计算结果是什么,它都可以工作(尽管它不那么漂亮):
(x.update(y), x)[-1]
如果尚未x
在变量中,则可以使用lambda
来创建局部变量,而无需使用赋值语句。这相当于使用let 表达式lambda
,这是函数式语言中的一种常见技术,但可能不符合 Python 风格。
(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 版本不支持该样式,let 表达式模式在这里也适用。
(lambda z=x.copy(): z.update(y) or z)()
(当然,这几乎等同于(z := x.copy()).update(y) or z
,但如果你的 Python 版本足够新,那么 PEP 448 风格将可用。)
解决方案 23:
由于PEP 572:赋值表达式, Python 3.8 发布时(计划于 2019 年 10 月 20 日)将有一个新选项。新的赋值表达式运算符允许您分配的结果并仍然使用它来调用,使组合代码成为一个表达式,而不是两个语句,从而改变::=
`copy`update
newdict = dict1.copy()
newdict.update(dict2)
到:
(newdict := dict1.copy()).update(dict2)
同时在各个方面表现相同。如果您还必须返回结果dict
(您要求一个返回 的表达式dict
;上面的代码创建并分配给newdict
,但不返回它,因此您不能使用它按原样将参数传递给函数,就像 一样myfunc((newdict := dict1.copy()).update(dict2))
),那么只需将其添加or newdict
到末尾(因为update
返回None
,它是假的,它将评估并返回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)
或者不使用 s 的临时元组None
,但对每个None
结果进行真实性测试:
(newdict := dict1.copy()).update(dict2) or newdict.update(dict3)
这两种方式显然都更加丑陋,并且效率更低(要么浪费临时tuple
的None
s 来分隔逗号,要么对每个update
's 的None
返回进行无意义的真实性测试来or
分隔)。
赋值表达式方法的唯一真正优势出现在以下情况:
您有需要处理
set
s 和dict
s 的通用代码(它们都支持copy
和update
,因此代码的工作原理大致与您预期的一样)您希望接收任意类似字典的对象,而不仅仅是
dict
它自己,并且必须保留左侧的类型和语义(而不是以普通的 结束dict
)。虽然myspecialdict({**speciala, **specialb})
可能有效,但它会涉及一个额外的临时dict
,并且如果myspecialdict
具有普通的dict
无法保留的功能(例如,常规dict
s 现在根据键的第一次出现保留顺序,并根据键的最后一次出现保留值;您可能需要一个根据键的最后一次出现保留顺序的,因此更新值也会将其移动到末尾),那么语义就是错误的。由于赋值表达式版本使用命名方法(可能已重载以正常运行),它根本不会创建dict
(除非dict1
已经是dict
),保留原始类型(和原始类型的语义),同时避免任何临时变量。
解决方案 24:
Python 3.9 中的新功能:使用联合运算符 (|
) 来合并dict
类似于set
s 的 s:
>>> d = {'a': 1, 'b': 2}
>>> e = {'a': 9, 'c': 3}
>>> d | e
{'a': 9, 'b': 2, 'c': 3}
对于匹配的密钥,右侧dict
优先。
这也适用于就地|=
修改:dict
>>> e |= d # e = e | d
>>> e
{'a': 1, 'c': 3, 'b': 2}
解决方案 25:
借鉴这里和其他地方的想法,我理解了一个功能:
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')==\n {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})
assert (merge(foo='bar')=={'foo': 'bar'})
assert (merge({1:11},{1:99},foo='bar',baz='quux')==\n {1: 99, 'foo': 'bar', 'baz':'quux'})
assert (merge({1:11},{1:99})=={1: 99})
您可以改用 lambda。
解决方案 26:
(仅适用于 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 a
中的位lambda
是必需的,因为dict.update
总是在成功时返回None
。)
解决方案 27:
我对迄今为止列出的解决方案存在的问题是,在合并的字典中,键“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}
解决方案 28:
它太愚蠢了,.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
解决方案 29:
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))
这应该可以解决你的问题。
解决方案 30:
这可以通过一个字典理解来完成:
>>> 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)
}
在我看来,“单一表达”部分的最佳答案是不需要额外的功能,而且很短。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件