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

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

>>> 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 ),第二个字典的值将覆盖第一个字典的值。

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

    这是一个新的语法,在PEP 448中提出,可用于Python 3.5

    z = {**x, **y}
    

    这确实是一个单一的表达。 现在它已经在3.5 PEP 478的发布时间表中显示出来了,现在它已经进入了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 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)
    

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

    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中适用于所有的字典。 例如给定的字母ag

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

    并且g键值对将优先于字母af ,依此类推。

    对其他答案的批评

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

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

    在Python 2中,为内存中的每个字典创建两个列表,在内存中创建第三个列表,长度等于前两个列表的长度,然后放弃所有三个列表以创建字典。 在Python 3中,这会失败,因为您将两个dict_items对象添加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() viewitems() Python 2.7中的viewitems()中的items()的联合时,如果值不可对对象(例如列表),也会失败。 即使你的值是可散列的, 因为集合在语义上是无序的,所以关于优先级的行为是未定义的。 所以不要这样做:

    >>> 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中正在使用的一个示例。

    字典意在采用可哈希键(例如frozensets或tuples),但是当这些键不是字符串时这种方法在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写道:

    我很好地声明字典({},** {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和Python 3.这仅适用于字符串键的事实是关键字参数如何工作的直接后果,而不是字典的短暂结果。 在这个地方也没有使用**操作符滥用机制,实际上**的设计恰恰是为了将关键字传递给dicts。

    再次,当键不是字符串时,它不适用于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日益被弃用。

    性能较差但正确的临时组

    这些方法性能较差,但它们会提供正确的行为。 与copyupdate或新的解包相比,它们的性能要差得多,因为它们在更高级别的抽象层次上迭代每个键值对,但它们确实遵守优先顺序(后面的字典有优先权)

    您也可以在dict理解中手动链接字典:

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

    词典资源

  • 我对Python的字典实现的解释,更新为3.6。
  • 回答如何将新密钥添加到字典中
  • 将两个列表映射到字典中
  • 官方Python文档的字典
  • 词典更强大 - 在2017年Pycon上布兰登罗德的谈话
  • 现代Python词典,伟大想法的汇合 - Raymond Hettinger在2017年Pycon上的演讲

  • 在你的情况下,你可以做的是:

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

    这将根据你的需要,把最后一个字典放在z ,并且让第二个字母( y )的值正确地覆盖键b的值:

    >>> x = {'a':1, 'b': 2}
    >>> y = {'b':10, 'c': 11}
    >>> z = dict(x.items() + y.items())
    >>> z
    {'a': 1, 'c': 11, 'b': 10}
    

    如果你使用Python 3,它只会更复杂一点。 要创建z

    >>> z = dict(list(x.items()) + list(y.items()))
    >>> z
    {'a': 1, 'c': 11, 'b': 10}
    

    替代:

    z = x.copy()
    z.update(y)
    
    链接地址: http://www.djcxy.com/p/361.html

    上一篇: How to merge two dictionaries in a single expression?

    下一篇: Check if a directory exists in a shell script