程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

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

发布于2019-08-19 21:41     阅读(1998)     评论(0)     点赞(19)     收藏(4)


我有两个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)

说明

假设您有两个dicts,并且您希望将它们合并到一个新的dict而不更改原始的dicts:

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}

它现在显示为在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)

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

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

对于所有dicts,此函数将在Python 2和3中使用。例如给出的a决定g

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

在键值对g的优先级高于类型的字典af,等等。

批评其他答案

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

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

在Python 2中,您在内存中为每个dict创建两个列表,在内存中创建第三个列表,其长度等于放在一起的前两个列表的长度,然后丢弃所有三个列表以创建dict。在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构造函数,并且非常快且内存效率高(甚至比我们的两步过程稍微多一些),但除非你确切知道这里发生了什么(也就是说,第二个dict作为关键字参数传递给dict构造函数),它很难阅读,它不是预期的用途,所以它不是Pythonic。

这是django修复的用法示例

Dicts旨在获取可散列密钥(例如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写道:

我很好地宣布dict({},** {1:3})是非法的,因为它毕竟是滥用**机制。

显然dict(x,** y)作为“调用x.update(y)并返回x”的“酷黑客”。就个人而言,我发现它比酷酷更卑鄙。

我的理解(以及对语言创建者的理解)的预期用途dict(**y)是为了可读性目的而创建dicts,例如:

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

代替

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

对评论的回应

尽管Guido说,dict(x, **y)这符合dict规范,顺便说一下。适用于Python 2和3.事实上,这仅适用于字符串键,这是关键字参数如何工作而不是dict短路的直接结果。在这个地方也没有使用**运算符滥用该机制,事实上**被精确地设计为将dicts作为关键字传递。

同样,当键是非字符串时,它不适用于3。隐式调用契约是命名空间采用普通的dicts,而用户只能传递字符串的关键字参数。所有其他callables强制执行它。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}似乎没有处理嵌套字典。嵌套键的内容被简单地覆盖,没有合并[...]我最终被这些没有递归合并的答案所灼烧,我很惊讶没有人提到它。在我对“合并”一词的解释中,这些答案描述了“用另一个更新一个字典”,而不是合并。

是。我必须回过头来回答这个问题,即要求两个词典浅层合并,第一个的值被第二个词覆盖 - 在一个表达式中。

假设有两个词典字典,一个可以在一个函数中递归地合并它们,但是你应该注意不要从任何一个源修改dicts,并且最可靠的方法是在分配值时复制它们。由于密钥必须是可清洗的,因此通常是不可变的,因此复制它们是没有意义的:

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

提出其他价值类型的突发事件远远超出了这个问题的范围,所以我将指出你对“词典合并词典”的规范问题的回答

性能较差但正确的Ad-hoc

这些方法性能较差,但它们会提供正确的行为。他们将少得多比高性能copyupdate或新的拆包,因为他们遍历在更高的抽象水平的每个键-值对,但他们做的尊重优先顺序(后者类型的字典具有优先权)

你也可以在dict理解中手动链接dicts:

{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

字典资源



所属网站分类: 技术文章 > 问答

作者:黑洞官方问答小能手

链接:https://www.pythonheidong.com/blog/article/48893/c08ad9646717f5674e71/

来源:python黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

19 0
收藏该文
已收藏

评论内容:(最多支持255个字符)