如何在一个表达式中合并两个词典?

时间 2008-09-02
阅读 1328684
点赞 3504
收藏 833
连接carl-meyer

我有两个python字典,我想编写一个表达式,返回合并后的这两个字典。这个update()方法将是我需要的,如果它返回其结果而不是就地修改dict。

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

我怎样才能把最后一个合并的词典z不是x

(更清楚的是,最后一个赢得了dict.update()也是我要找的。)

✅ 被采纳的答案

如何在一个表达式中合并两个python字典?

对于字典xyz变为具有以下值的浅合并字典y把那些从x.

  • 在python 3.5或更高版本中:

    z = {**x, **y}
    
  • 在python 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
    

    现在:

    z = merge_two_dicts(x, y)
    

解释

假设您有两个听写,并且希望在不更改原始听写的情况下将它们合并为新的听写:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

期望的结果是得到一本新字典(z)值合并后,第二个dict的值将覆盖第一个dict的值。

>>> z
{'a': 1, 'b': 3, 'c': 4}

新的语法,在PEP 448available 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文件。

但是,由于许多组织仍在使用Python2,因此您可能希望以向后兼容的方式进行此操作。在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 dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

然后有一个表达式:

z = merge_two_dicts(x, y)

您还可以创建一个函数来合并未定义数量的dict,从零到一个非常大的数字:

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

这个函数将在python 2和3中为所有dict工作。例如口述ag

z = merge_dicts(a, b, c, d, e, f, g) 

和键值对g将优先于听写af等等。

对其他答案的评论

不要使用您在以前接受的答案中看到的内容:

z = dict(x.items() + y.items())

在python 2中,为每个dict在内存中创建两个列表,在内存中创建第三个长度等于前两个列表的长度的列表,然后丢弃所有三个列表来创建dict。在Python3中,这将失败因为你加了两个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中(viewitems()在python 2.7中,当值是不可显示的对象(例如列表)时,也会失败。即使您的值是可哈希的,由于集合在语义上无序,因此该行为在优先级方面未定义。所以不要这样做:

>>> 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.

dict用于获取哈希键(例如frozenset或tuples),但是当键不是字符串时,此方法在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语言的创造者吉多·范·罗森写道:

我很好 声明dict(,**1:3)非法,因为它毕竟是滥用 **机制。

显然,dict(x,**y)是“酷黑客”for“call”的意思。 x.更新(y)并返回x“。我个人觉得这比 酷。

这是我的理解(以及对creator of the language)预期用途dict(**y)用于创建用于可读性目的的dict,例如:

dict(a=1, b=10, c=11)

而不是

{'a': 1, 'b': 10, 'c': 11}

对评论的回应

不管吉多怎么说,dict(x, **y)符合dict规范,它对python 2和3都有效。这只适用于字符串键的事实是关键字参数如何工作的直接结果,而不是dict的简短混合。在这里也不使用**运算符滥用该机制,事实上**被精确地设计为将dict作为关键字传递。

同样,当键是非字符串时,它不适用于3。隐式调用约定是命名空间采用普通的dict,而用户只能传递字符串的关键字参数。所有其他可调用的强制执行。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())仍然是Python2最可读的解决方案。可读性计数。

我的回答是:merge_two_dicts(x, y)实际上,如果我们真的关心可读性,对我来说似乎更清楚了。而且它不是向前兼容的,因为Python2越来越不受欢迎。

{**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: {}}}

为其他值类型提出意外事件远远超出了这个问题的范围,所以我将向您指出my answer to the canonical question on a "Dictionaries of dictionaries merge".

性能较差但正确的广告骗局

这些方法的性能较差,但它们将提供正确的行为。 他们将少得多性能比copyupdate或者新的解包,因为它们在更高的抽象级别上迭代每个键值对,但是它们遵守优先顺序(后一句话优先)

您还可以在听写理解中手动链接听写:

{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())

itertools.chain将按正确的顺序在键值对上链接迭代器:

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

性能分析

我只会对已知行为正确的用法进行性能分析。

import timeit

以下是在Ubuntu 14.04上完成的

在python 2.7(系统python)中:

>>> 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

在python 3.5(死蛇ppa)中:

>>> 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

词典资源

👍 3771