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

卡尔·迈耶

我有两个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.9.0或更高(释放2020年10月17日):PEP-584在这里讨论,被实现,并且提供了最简单的方法:

    z = x | y          # NOTE: 3.9+ ONLY
    
  • 在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}

而且确实是一个单一的表达。

请注意,我们也可以使用文字符号合并:

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 dictionaries, 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 dictionaries, shallow copy and merge into a new dict,
    precedence goes to key-value pairs in latter dictionaries.
    """
    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对象而不是两个列表在一起-

>>> 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修复的用法的示例

字典旨在采用可散列的键(例如,frozenset或元组),但是当键不是字符串时此方法在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)是出于可读性目的创建字典,例如:

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

代替

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

对评论的回应

尽管Guido说了什么dict(x, **y),但符合dict规范,顺便说一句。它仅适用于Python 2和3。事实上,这仅适用于字符串键,这是关键字参数如何工作的直接结果,而不是dict的缺点。在此位置使用**运算符也不会滥用该机制,实际上,**的设计目的是将字典作为关键字进行传递。

同样,当键为非字符串时,它不适用于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越来越不推荐使用。

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

提出其他价值类型的突发事件远远超出了此问题的范围,因此,我将针对“字典合并字典”中的规范问题向您指出

性能较差但临时性正确

这些方法的性能较差,但是它们将提供正确的行为。它们的性能不及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()) # iteritems in Python 2

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

from itertools import chain
z = dict(chain(x.items(), y.items())) # iteritems in Python 2

绩效分析

我将仅对已知行为正确的用法进行性能分析。(自包含的,因此您可以复制并粘贴自己。)

from timeit import repeat
from itertools import chain

x = dict.fromkeys('abcdefg')
y = dict.fromkeys('efghijk')

def merge_two_dicts(x, y):
    z = x.copy()
    z.update(y)
    return z

min(repeat(lambda: {**x, **y}))
min(repeat(lambda: merge_two_dicts(x, y)))
min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
min(repeat(lambda: dict(chain(x.items(), y.items()))))
min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))

在Python 3.8.1中,NixOS:

>>> min(repeat(lambda: {**x, **y}))
1.0804965235292912
>>> min(repeat(lambda: merge_two_dicts(x, y)))
1.636518670246005
>>> min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
3.1779992282390594
>>> min(repeat(lambda: dict(chain(x.items(), y.items()))))
2.740647904574871
>>> min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))
4.266070580109954
$ uname -a
Linux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux

词典资源

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在Python中的单个表达式中合并两个词典(合并词典)?

如何在同一词典下合并两个嵌套词典

如何在Keras Tensoflow中合并两个模型以创建一个模型

正则表达式:合并两个组

如何在一个ObjectsList中合并两个或多个对象

如何使用.apply()将一列词典合并为一个词典?

合并必须至少包含两个表达式

如何在一个表达式中合并两个复杂的字典?

如何使用Julia合并单个词典中的两个词典?

如何在SQLAlchemy中将多个正则表达式合并为一个?

使用公共ID密钥合并两个词典列表

如何在PySpark中合并并将两个rdd合并为一个

如何合并两个和并分解出一个公共元素表达式?

无法将两个词典合并为一本词典

如何使用Python将三个词典列表合并到一个嵌套词典中

如何在一个逻辑卷中合并两个分区?

合并两个正则表达式

如何在一个解决方案中合并两个Web项目

如何在一个noUiSlider中合并两个因变量?

试图将两个单独的正则表达式合并为一个

如何合并两个通配符表达式

合并两个转换表达式

如何合并两个仅包含唯一值的词典

如何合并/合并两个正则表达式?

将两个正则表达式合并为一个正则表达式

如何使用一个表达式与 lambda 合并两个计数值?

如何将两个 xpath 表达式合并为一个?

将两个检查 url 的正则表达式合并为一个

如何在熊猫中将两个正则表达式合并为一个命令