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

我有两个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中适用于所有的字典。例如教http://stardict.sourceforge.net/Dictionaries.php下载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对象添加到一起,而不是两个列表 –

>>> 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构造函数,并且速度非常快并且内存效率更高(甚至比我们的两步过程稍微多一点),但除非您确切知道这里发生了什么(也就是说,第二个字典是作为关键字参数传递给字典构造函数),它很难阅读,这不是预期的用法,所以它不是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)符合字典规范,这顺便说一句。适用于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

词典资源

添加评论

友情链接:蝴蝶教程