我想将两个字典合并成一个新字典。
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)
>>> z
{'a': 1, 'b': 3, 'c': 4}
每当一个键 k
存在于两个字典中时,只有值 y[k]
应该被保留。
我想将两个字典合并成一个新字典。
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)
>>> z
{'a': 1, 'b': 3, 'c': 4}
每当一个键 k
存在于两个字典中时,只有值 y[k]
应该被保留。
In Python 3.9.0 or greater (released 17 October 2020, PEP-584
, discussed here):
z = x | y
In Python 3.5 or greater:
z = {**x, **y}
In Python 2, (or 3.4 or lower) write a function:
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
and now:
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}
现在已经在Python 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 或需要编写向后兼容的代码,并且你想要使用单个表达式,最优的正确方法是将其放在一个函数中:
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
z = merge_dicts(a, b, c, d, e, f, g)
在字典 a
到 f
中,键值对将被字典 g
中的键值对覆盖。
不要参考以前被接受的答案:
z = dict(x.items() + y.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)
>>> 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)
符合字典规范,并且适用于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: {}}}
这个问题涉及到其他类型的值的备选方案已经超出了本问题的范畴,所以我将指向我在“字典合并”规范问题上的答案。
这些方法虽然正确,但效率较低。它们比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())))
>>> 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
在您的情况下,您可以执行:
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}
另一个选择:
z = x.copy()
z.update(y)
(lambda z: z.update(y) or z)(x.copy())
:P - towr另一种更简洁的选择:
z = dict(x, **y)
注意:虽然这个方法已经变得很流行,但需要指出的是,如果 y
具有任何非字符串键,那么这种方法实际上是滥用了 CPython 实现细节,它在 Python 3、PyPy、IronPython 或 Jython 中都无法工作。此外,Guido 不赞成使用此方法。因此,我不能建议在具备向前兼容性或跨平台可移植性的代码中使用此技术,这意味着应该完全避免使用此方法。
这可能不是一个受欢迎的答案,但您几乎肯定不想这样做。如果您想要一个合并的副本,则使用copy(或deepcopy,具体取决于您的需求)然后进行更新。这两行代码更易读 - 更符合Pythonic风格 - 比单行创建带有.items() + .items()的代码更容易理解。显式优于隐式。
此外,当您使用.items()(在Python 3.0之前)时,您正在创建一个包含字典项的新列表。如果您的字典很大,那么这将是相当多的开销(两个大列表将立即被丢弃,一旦合并的字典被创建)。update()可以更有效地工作,因为它可以逐个遍历第二个dict的项。
就time而言:
>>> 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()和update()将在较旧版本中起作用。
z1 = dict(x.items() + y.items())
z2 = dict(x, **y)
% 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
的获胜大约是 z1
的十倍。在我的观点中,这算是一次相当大的胜利!
在比较了这两个之后,我想知道 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核心。Python在许多地方使用dict
;优化其操作非常重要。您也可以将其写成:z0 = x.copy()
z0.update(y)
就像Tony所做的那样,但(不出所料)符号表示上的差异对性能没有任何可测量的影响。 选择看起来正确的方法即可。 当然,他绝对正确地指出两个语句版本要容易理解得多。
items()
不可连接,而iteritems()
不存在。 - Antti Haapala -- Слава Україні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}
del
时,将删除该键的第一个映射。 - Slayerdict
来避免这种情况,即:dict(ChainMap({}, y, x))
- wjandreaChainMap
,非常感谢! - run_the_race我想要类似的东西,但希望能够指定如何合并重复键上的值,因此我进行了一些修改(但没有进行大量测试)。显然,这不是一个单一的表达式,但它只需要一个函数调用。
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
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
}
}
我用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()
TypeError: copy_update()需要1个位置参数,但提供了2个
- Aaditya Urax_1 | x_2 | ... | x_n
。 - william_grisaitis
{**{(0, 1):2}}
→{(0, 1): 2}
。 - Russia Must Remove Putinx | y
。 - Callam Delaney