我有两个Python字典,我想编写一个表达式来返回这两个字典并置。如果
1 2 3 4 5 6 7 | >>> x = {'a':1, 'b': 2} >>> y = {'b':10, 'c': 11} >>> z = x.update(y) >>> print(z) None >>> x {'a': 1, 'b': 10, 'c': 11} |
如何在
(需要特别说明的是,我也在寻找处理
How can I merge two Python dictionaries in a single expression?
对于字典
在python3.5或以上版本:
1 | z = {**x, **y} |
在python2中,(或3.4或更低)写一个函数:
1 2 3 4 | def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values z.update(y) # modifies z with y's keys and values & returns None return z |
现在:
1 | z = merge_two_dicts(x, y) |
解释
假设您有两个dict,您想将它们合并到一个新的dict中,而不更改原来的dict:
1 2 | x = {'a': 1, 'b': 2} y = {'b': 3, 'c': 4} |
所需的结果是得到一个合并了值的新字典(
1 2 | >>> z {'a': 1, 'b': 3, 'c': 4} |
为此,在PEP 448中提出了一种新的语法,并在Python 3.5中可用
1 | z = {**x, **y} |
这确实是一个简单的表达。
注意,我们也可以合并文字符号:
1 | z = {**x, 'foo': 1, 'bar': 2, **y} |
现在:
1 2 | >>> 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中提供的经典Python方法是将此过程分为两步:
1 2 | z = x.copy() z.update(y) # which returns None since it mutates z |
在这两种方法中,
如果你还没有使用Python 3.5,或者需要编写向后兼容的代码,并且你想把它放在一个表达式中,那么最有效的方法就是把它放在一个函数中:
1 2 3 4 5 | def merge_two_dicts(x, y): """Given two dicts, merge them into a new dict as a shallow copy.""" z = x.copy() z.update(y) return z |
然后你有一个简单的表达式:
1 | z = merge_two_dicts(x, y) |
您还可以创建一个函数来合并一个未定义的dicts数量,从0到一个非常大的数字:
1 2 3 4 5 6 7 8 9 | def merge_dicts(*dict_args): """ Given any number of dicts, shallow copy and merge into a new dict, precedence goes to key value pairs in latter dicts. """ result = {} for dictionary in dict_args: result.update(dictionary) return result |
这个函数在python2和python3中适用于所有dict。例如,给定
1 | z = merge_dicts(a, b, c, d, e, f, g) |
而
不要使用你在之前被接受的答案中看到的:
1 | z = dict(x.items() + y.items()) |
在Python 2中,您创建了两个列表为每个dict类型在内存中,在内存中创建第三个列表的长度等于前两个放在一起,然后丢弃所有三个列表创建关键字。在Python 3中,这将会失败,因为你要添加两个
1 2 3 4 | >>> 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' |
您必须显式地将它们创建为列表,例如
类似地,在Python 3中使用
1 | >>> c = dict(a.items() | b.items()) |
这个例子演示了当值不可刷新时会发生什么:
1 2 3 4 5 6 | >>> 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的值被保留:
1 2 3 4 | >>> x = {'a': 2} >>> y = {'a': 1} >>> dict(x.items() | y.items()) {'a': 2} |
另一个你不应该使用的黑客:
1 | z = dict(x, **y) |
这个构造函数使用
下面是在django中纠正这种用法的一个例子。
dict的目的是获取可替换的键(例如frozensets或tuple),但是当键不是字符串时,这种方法在python3中会失败。
1 2 3 4 | >>> c = dict(a, **b) Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: keyword arguments must be strings |
在邮件列表中,该语言的创建者Guido van Rossum写道:
I am fine with
declaring dict({}, **{1:3}) illegal, since after all it is abuse of
the ** mechanism.
和
Apparently dict(x, **y) is going around as"cool hack" for"call
x.update(y) and return x". Personally I find it more despicable than
cool.
根据我的理解(以及该语言的创建者的理解),
1 | dict(a=1, b=10, c=11) |
而不是
1 | {'a': 1, 'b': 10, 'c': 11} |
回应评论
Despite what Guido says,
dict(x, **y) is in line with the dict specification, which btw. works for both Python 2 and 3. The fact that this only works for string keys is a direct consequence of how keyword parameters work and not a short-comming of dict. Nor is using the ** operator in this place an abuse of the mechanism, in fact ** was designed precisely to pass dicts as keywords.
同样,当键是非字符串时,它对3不起作用。隐式调用契约是名称空间采用普通的dict,而用户必须只传递字符串关键字参数。所有其他可调用项都强制执行它。
1 2 3 4 5 6 | >>> 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),这种不一致性很糟糕。因此,它在python3中得到了修复,因为这种用法可能是一个突破性的变化。
我向您提交的意见是,故意编写只在一种语言的一个版本中有效的代码,或者只在特定的任意约束下有效的代码,这是恶意的无能。
更多的评论:
dict(x.items() + y.items()) is still the most readable solution for Python 2. Readability counts.
我的回答是:
{**x, **y} does not seem to handle nested dictionaries. the contents of nested keys are simply overwritten, not merged [...] I ended up being burnt by these answers that do not merge recursively and I was surprised no one mentioned it. In my interpretation of the word"merging" these answers describe"updating one dict with another", and not merging.
是的。我必须让您回到这个问题,它要求两个字典进行浅合并,第一个字典的值被第二个字典的值覆盖——在一个表达式中。
假设有两个字典字典,其中一个可能递归地将它们合并到一个函数中,但是您应该注意不要修改来自任何一个源的字典,避免这种情况的最可靠的方法是在赋值时进行复制。由于密钥必须是可加锁的,因此通常是不可变的,复制它们是没有意义的:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
用法:
1 2 3 4 | >>> 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这些方法的性能较差,但是它们将提供正确的行为。它们的性能将远低于
您还可以在一个dict理解中手动链接dict:
1 | {k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7 |
或者在python 2.6中(可能早在2.4引入生成器表达式时):
1 | dict((k, v) for d in dicts for k, v in d.items()) |
1 2 | import itertools z = dict(itertools.chain(x.iteritems(), y.iteritems())) |
性能分析
我只打算对已知的行为正确的用法进行性能分析。
1 | import timeit |
下面是在Ubuntu 14.04上完成的
在Python 2.7 (system Python)中:
1 2 3 4 5 6 7 8 | >>> min(timeit.repeat(lambda: merge_two_dicts(x, y))) 0.5726828575134277 >>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} )) 1.163769006729126 >>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems())))) 1.1614501476287842 >>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items()))) 2.2345519065856934 |
在python3.5 (deadsnake PPA)中:
1 2 3 4 5 6 7 8 9 10 | >>> min(timeit.repeat(lambda: {**x, **y})) 0.4094954460160807 >>> min(timeit.repeat(lambda: merge_two_dicts(x, y))) 0.7881555100320838 >>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} )) 1.4525277839857154 >>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items())))) 2.3143140770262107 >>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items()))) 3.2069112799945287 |
资源字典我对Python字典实现的解释,更新为3.6。回答如何向字典中添加新键将两个列表映射到一个字典中关于字典的官方Python文档布兰登·罗兹2017年在Pycon大会上的演讲更有力《现代Python词典》,汇集了许多伟大的思想——Raymond Hettinger在2017 Pycon大会上的演讲
在你的情况下,你可以做的是:
1 | z = dict(x.items() + y.items()) |
如您所愿,这将把最后一个dict放入
1 2 3 4 5 | >>> x = {'a':1, 'b': 2} >>> y = {'b':10, 'c': 11} >>> z = dict(x.items() + y.items()) >>> z {'a': 1, 'c': 11, 'b': 10} |
如果使用python3,它只会稍微复杂一点。创建
1 2 3 | >>> z = dict(list(x.items()) + list(y.items())) >>> z {'a': 1, 'c': 11, 'b': 10} |
另一个:
1 2 | z = x.copy() z.update(y) |
另一个更简洁的选项是:
1 | z = dict(x, **y) |
注意:这已经成为一个流行的答案,但重要的是要指出,如果
这可能不是一个流行的答案,但是您几乎肯定不希望这样做。如果您想要一个合并的副本,那么使用copy(或deepcopy,这取决于您想要什么),然后更新。与使用.items() + .items()创建单行代码相比,这两行代码更具可读性——更符合python风格。显式比隐式好。
此外,当您使用.items() (pre - Python 3.0)时,您将创建一个包含dict项的新列表。update()可以更有效地工作,因为它可以逐项运行第二个dict。
在时间方面:
1 2 3 4 5 6 7 8 9 10 | >>> 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()在较早的版本中可以工作。
在后续的回答中,您询问了这两种方法的相对性能:
1 2 | z1 = dict(x.items() + y.items()) z2 = dict(x, **y) |
至少在我的机器上(一个相当普通的运行Python 2.5.2的x86_64), alternative
例1:相同的字典将20个连续整数映射到它们自己:
1 2 3 4 | % 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 |
例2:非重叠字典将252个短字符串映射为整数,反之亦然:
1 2 3 4 | % 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 |
在比较了这两个项目之后,我想知道
1 2 | from itertools import chain z3 = dict(chain(x.iteritems(), y.iteritems())) |
一些快速的测试,例如。
1 2 | % 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 |
让我得出这样的结论:
这个讨论仍然缺少一些重要的东西,即将这些替代方案与合并两个列表的"明显"方式(使用
1 2 | z0 = dict(x) z0.update(y) |
一个典型的结果:
1 2 | % 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 |
换句话说,
事实上,我甚至认为纯Python代码不可能做得比这更好。如果您可以在C扩展模块中做得更好,我想Python人员可能会对将您的代码(或方法的变体)合并到Python核心中很感兴趣。Python在很多地方使用
你也可以这样写
1 2 | z0 = x.copy() z0.update(y) |
正如Tony所做的那样,但是(毫不奇怪)符号上的差异对性能没有任何可测量的影响。用你认为合适的。当然,他指出两句话的版本更容易理解是绝对正确的。
我想要类似的东西,但是能够指定如何合并重复键上的值,所以我删除了这个(但没有进行大量测试)。显然,这不是一个表达式,而是一个函数调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 |
在python3中,您可以使用集合。ChainMap将多个dicts或其他映射组合在一起,创建一个单一的、可更新的视图:
1 2 3 4 5 6 7 8 9 10 | >>> from collections import ChainMap >>> x = {'a':1, 'b': 2} >>> y = {'b':10, 'c': 11} >>> z = ChainMap({}, y, x) >>> for k, v in z.items(): print(k, '-->', v) a --> 1 b --> 10 c --> 11 |
递归/深度更新dict
1 2 3 4 5 6 7 8 9 10 11 | 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 |
演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | pluto_original = { 'name': 'Pluto', 'details': { 'tail': True, 'color': 'orange' } } pluto_update = { 'name': 'Pluutoo', 'details': { 'color': 'blue' } } print deepupdate(pluto_original, pluto_update) |
输出:
1 2 3 4 5 6 7 | { 'name': 'Pluutoo', 'details': { 'color': 'blue', 'tail': True } } |
感谢rednaw的编辑。
在不使用copy的情况下,我能想到的最好的版本是:
1 2 3 4 | from itertools import chain x = {'a':1, 'b': 2} y = {'b':10, 'c': 11} dict(chain(x.iteritems(), y.iteritems())) |
它比
我个人最喜欢这个版本,因为它用一个函数语法很好地描述了我想要的东西。唯一的小问题是,y的值优先于x的值并不是很明显,但我不认为这很难算出来。
Python 3.5 (PEP 448)允许更好的语法选项:
1 2 3 4 5 | x = {'a': 1, 'b': 1} y = {'a': 2, 'c': 2} final = {**x, **y} final # {'a': 2, 'b': 1, 'c': 2} |
甚至
1 | final = {'a': 1, 'b': 1, **x, **y} |
1 2 3 4 | x = {'a':1, 'b': 2} y = {'b':10, 'c': 11} z = dict(x.items() + y.items()) print z |
对于在两个字典中都有键的项('b'),您可以通过将该项放在最后来控制输出中的哪个项。
虽然这个问题已经被回答过好几次了,这个简单的解决方案还没有列出。
1 2 3 4 5 | x = {'a':1, 'b': 2} y = {'b':10, 'c': 11} z4 = {} z4.update(x) z4.update(y) |
它和上面提到的z0和邪恶的z2一样快,但是很容易理解和改变。
1 2 3 4 5 6 | def dict_merge(a, b): c = a.copy() c.update(b) return c new = dict_merge(old, extras) |
在这些可疑的答案中,这个闪亮的例子是唯一一个也是唯一一个在Python中合并dicts的好方法,它得到了独裁者Guido van Rossum本人的支持!还有人提出了一半的建议,但没有把它放入函数中。
1 2 3 | print dict_merge( {'color':'red', 'model':'Mini'}, {'model':'Ferrari', 'owner':'Carl'}) |
给:
1 | {'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'} |
如果你认为拉姆达斯是邪恶的,那就不要再读下去了。根据要求,你可以用一个表达式写出快速和内存效率高的解决方案:
1 2 3 4 5 6 7 | 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} |
如上所述,使用两行代码或编写函数可能是更好的方法。
神谕的。使用一种理解:
1 2 3 4 | z={i:d[i] for d in [x,y] for i in d} >>> print z {'a': 1, 'c': 11, 'b': 10} |
在python3中,
1 | dict(x.items() | y.items()) |
对于2.7版本中类似python的行为,
1 | dict(x.viewitems() | y.viewitems()) |
无论如何,我更喜欢这种表示法,因为将它看作集合联合操作而不是连接(如标题所示)似乎更自然。
编辑:
python 3还有几个要点。首先,请注意,除非
此外,Raymond Hettinger的Chainmap答案相当优雅,因为它可以接受任意数量的dict作为参数,但是从文档来看,它看起来像是依次查看每个查找的所有dict列表:
Lookups search the underlying mappings successively until a key is found.
如果你在你的应用程序中有很多查找,这可能会减慢你的速度:
1 2 3 4 5 6 7 8 | 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的粉丝,但是在有很多查找的地方看起来不太实用。
虐待导致马修的答案只有一种表达方式:
1 2 3 4 5 | >>> 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} |
您说需要一个表达式,所以我滥用了
当然,如果你不介意复制的话,你也可以这样做:
1 2 3 4 5 | >>> x = {'a':1, 'b': 2} >>> y = {'b':10, 'c': 11} >>> z = (x.update(y), x)[1] >>> z {'a': 1, 'b': 10, 'c': 11} |
使用保持顺序的itertools的简单解决方案(后面的词具有优先级)
1 2 | import itertools as it merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args))) |
的用法:
1 2 3 4 5 6 7 8 | >>> 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} |
两本词典
1 2 | def union2(dict1, dict2): return dict(list(dict1.items()) + list(dict2.items())) |
n字典
1 2 | def union(*dicts): return dict(itertools.chain.from_iterable(dct.items() for dct in dicts)) |
在python 3:
1 2 3 4 5 6 7 | import collections a = {1: 1, 2: 2} b = {2: 3, 3: 4} c = {3: 5} r = dict(collections.ChainMap(a, b, c)) print(r) |
:
1 | {1: 1, 2: 2, 3: 4} |
文档:https://docs.python.org/3/library/collections.html # collections.ChainMap:
即使答案对于这个浅字典很好,这里定义的所有方法实际上都没有做深字典合并。
例子:
1 2 3 | a = { 'one': { 'depth_2': True }, 'two': True } b = { 'one': { 'extra': False } } print dict(a.items() + b.items()) |
人们会期待这样的结果:
1 | { 'one': { 'extra': False', 'depth_2': True }, 'two': True } |
相反,我们得到:
1 | {'two': True, 'one': {'extra': False}} |
如果"one"条目确实是一个合并项,那么它的字典中应该有"depth_2"和"extra"作为条目。
使用链也,不工作:
1 2 | from itertools import chain print dict(chain(a.iteritems(), b.iteritems())) |
结果:
1 | {'two': True, 'one': {'extra': False}} |
rcwesick给出的深度合并也产生了相同的结果。
是的,合并示例字典是可行的,但是它们都不是通用的合并机制。稍后,当我编写一个真正进行合并的方法时,我将对此进行更新。
对于python2:
1 2 3 4 | x = {'a':1, 'b': 2} y = {'b':10, 'c': 11} z = dict(x.items()+y.items()) print(z) |
Python 3:
1 2 3 4 | x = {'a':1, 'b': 2} y = {'b':10, 'c': 11} z = dict(x.items()|y.items()) print(z) |
它给输出:
利用这里和其他地方的想法,我理解了一个函数:
1 2 | def merge(*dicts, **kv): return { k:v for d in list(dicts) + [kv] for k,v in d.items() } |
用法(在python3中测试):
1 2 3 4 5 6 7 8 9 | 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}) |
你可以用代替。
这可以用一个单词理解:
1 2 3 4 5 | >>> 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) } |
在我看来,"单一表达式"部分的最佳答案是不需要额外的函数,而且它很短。
到目前为止,我所列出的解决方案的问题是,在合并的字典中,键"b"的值是10,但是在我看来,它应该是12。为此,我提出以下建议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
结果:
1 2 3 4 5 6 | 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} |
1 2 3 4 5 6 7 8 9 | >>> 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} |
在Python 3.5中,您可以使用unpack
1 2 3 4 5 | dict1 = {'a':1} dict2 = {'b':2} new_dict = {**dict1, **dict2} >>>new_dict {'a':1, 'a':2} |
1 2 3 4 | from collections import Counter dict1 = {'a':1, 'b': 2} dict2 = {'b':10, 'c': 11} result = dict(Counter(dict1) + Counter(dict2)) |
这应该能解决你的问题。
(仅供Python2.7 *;Python3*有更简单的解决方案。)
如果您不反对导入标准库模块,您可以这样做
1 2 3 4 | from functools import reduce def merge_dicts(*dicts): return reduce(lambda a, d: a.update(d) or a, dicts, {}) |
(
您可以为此使用
太愚蠢了,
1 2 3 4 | def merge(dict1,*dicts): for dict2 in dicts: dict1.update(dict2) return dict1 |
例子:
1 2 3 4 | merge(dict1,dict2) merge(dict1,dict2,dict3) merge(dict1,dict2,dict3,dict4) merge({},dict1,dict2) # this one returns a new copy |
使用单词理解,你可以
1 2 3 4 5 | 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()))} |
给了
1 2 | >>> dc {'a': 1, 'c': 11, 'b': 10} |
注意理解中的
1 2 | { (some_key if condition else default_key):(something_if_true if condition else something_if_false) for key, value in dict_.items() } |
OP的两本词典的结合应该是这样的:
1 | {'a': 1, 'b': 2, 10, 'c': 11} |
具体地说,两个实体(
我下面的代码既不优雅也不是一行代码,但是我相信它与union的含义是一致的。
从OP的例子中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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中,这相当简单,可以使用我文章末尾所示的代码来完成。在此前提下,原问题的答案将是:
1 | z = MergeDict(x, y) |
当使用这个新对象时,它将表现得像一个合并的字典,但是它将有固定的创建时间和固定的内存占用,而原始字典则保持不变。创建它比提出的其他解决方案要便宜得多。
当然,如果您经常使用这个结果,那么您将在某个时候达到一个极限,创建一个真正的合并字典将是更快的解决方案。正如我所说,这取决于您的用例。
如果您曾经想要一个真正的合并的
你也可以使用这个类来制作一种"写中复制"的字典:
1 2 3 4 5 | a = { 'x': 3, 'y': 4 } b = MergeDict(a) # we merge just one dict b['x'] = 5 print b # will print {'x': 5, 'y': 4} print a # will print {'y': 4, 'x': 3} |
下面是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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: pass raise 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, v found.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()) |
如果您不介意修改
1 | x.update(y) or x |
简单,可读性高,性能。您知道
在标准库中修改方法,比如
如果您使用的库不遵循这种约定,您可以使用tuple display和index使其成为单个表达式,而不是
1 | (x.update(y), x)[-1] |
如果变量中还没有
1 | (lambda x: x.update(y) or x)({'a':1, 'b': 2}) |
如果您确实想要一个副本,PEP 448是最好的
1 | (lambda z: z.update(y) or z)(x.copy()) |
我很好奇,我是否可以用一行字符串化的方法来击败这个被接受的答案:
我尝试了5种方法,之前都没有提到——都是一行代码——都给出了正确的答案——但我无法接近。
所以…为了省去你的麻烦,也许满足你的好奇心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | import json import yaml import time from ast import literal_eval as literal def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values z.update(y) # modifies z with y's keys and values & returns None return 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') |
结果:
1 2 3 4 5 6 | 7.693928956985474 {'c': 11, 'b': 10, 'a': 1} stringify yaml 0.29134678840637207 {'c': 11, 'b': 10, 'a': 1} stringify literal 0.2208399772644043 {'c': 11, 'b': 10, 'a': 1} stringify eval 0.1106564998626709 {'c': 11, 'b': 10, 'a': 1} stringify replace 0.07989692687988281 {'c': 11, 'b': 10, 'a': 1} stringify json 0.005082368850708008 {'c': 11, 'b': 10, 'a': 1} accepted |
我从中学到的是
是的,我知道这不是最好的/正确的方式,所以请不要拒绝投票否定遗忘,零就好。我很好奇它是不是更快,但事实并非如此;我发帖就是为了证明这一点。
这个问题被标记为
1 2 3 4 5 6 7 8 9 10 11 | $ python2 Python 2.7.13 (default, Jan 19 2017, 14:48:08) [GCC 6.3.0 20170118] on linux2 Type"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} >>> ... |
我很高兴地说,上面的方法在python3的任何版本上都不再适用了。
这是Python 3.5或更高版本的表达式,使用
1 2 3 4 | >>> 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} |
注意:即使字典列表是空的或者只包含一个元素,也可以这样做。
我想我那些丑陋的俏皮话在这里是必要的。
1 2 3 | z = next(z.update(y) or z for z in [x.copy()]) # or z = (lambda z: z.update(y) or z)(x.copy()) |
字典是合并。单一的表达式。永远不要使用它。
附注:这是一个可以在Python的两个版本中工作的解决方案。我知道python3有这个
由于PEP 572:赋值表达式,Python 3.8发行版(计划于2019年10月20日发布)将提供一个新选项。新的赋值表达式运算符
1 2 | newdict = dict1.copy() newdict.update(dict2) |
:
1 | (newdict := dict1.copy()).update(dict2) |
却在各方面表现相同。如果您还必须返回结果
1 | (newdict := dict1.copy()).update(dict2) or newdict |
重要提示:一般来说,我不建议使用这种方法,而是支持:
1 | newdict = {**dict1, **dict2} |
解包方法清晰(谁知道广义开箱首先,你应该),根本不需要结果的名称(因此更简洁,当建立一个临时立即传递给一个函数或包含在
1 2 3 | newdict = {} newdict.update(dict1) newdict.update(dict2) |
但是在C层使用具体的
它也更具扩展性,因为合并三个
1 | newdict = {**dict1, **dict2, **dict3} |
使用赋值表达式不会像那样缩放;你能得到的最接近的答案是:
1 | (newdict := dict1.copy()).update(dict2), newdict.update(dict3) |
或者不使用
1 | (newdict := dict1.copy()).update(dict2) or newdict.update(dict3) |
显然,这两种方法中的任何一种都更加丑陋,并且还包括进一步的低效性(要么浪费一个临时的
赋值表达式方法的唯一真正优势出现在以下情况:
您有一个通用代码,它需要同时处理我认为你应该做以下事情:
1 | z = dict(x.items() + y.items()) |
我有一个没有在这里指定的解决方案(Man I LOVE python):-)
1 2 | z = {} z.update(x) or z.update(y) |
这不会像更新y一样更新x。我认为它不会慢得可怕。
注意:它应该是'or' operation and not 'and' operation。编辑以纠正代码。
一个快速的解决方案是:
1 | z4 = x.copy().update(y) |