gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 21:38:53 25 4
gpt4 key购买 nike

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



请注意,有一个 proposal (PEP 584)discussed here,可以通过给 dict一个合并运算符(预期为 |)来进一步简化此Python的未来版本,这将允许:

z = x | y        # <- But this is not yet implemented.


说明

假设您有两个字典,并且想要将它们合并为新字典而不更改原始字典:

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


理想的结果是获得一个合并了值的新字典( z),而第二个字典的值将覆盖第一个字典的值。

>>> 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文档中。

但是,由于许多组织仍在使用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)


您还可以创建一个函数来合并未定义数量的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中适用于所有字典。例如给定从 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()))。这浪费了资源和计算能力。

同样,当值是不可散列的对象(例如列表)时,在Python 3中使用 items()的并集(在Python 2.7中使用 viewitems())也将失败。即使您的值是可哈希的,由于集合在语义上是无序的,因此关于优先级的行为是不确定的。所以不要这样做:

>>> 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构造函数),很难阅读,它不是预期的用法,因此不是Pythonic。

这是用法为 remediated in 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在 mailing list中写道:


  我很好
  宣布dict({},** {1:3})非法,因为这毕竟是对
  **机制。





  显然,dict(x,** y)被“呼叫”称为“酷技巧”
  x.update(y)并返回x“。就我个人而言,它比
  凉。


根据我的理解(以及对 creator of the language的理解), 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: {}}}


提出其他值类型的意外情况远远超出了此问题的范围,因此我将向您指出 my answer to the canonical question on a "Dictionaries of dictionaries merge"

性能较差但临时性正确

这些方法的性能较差,但是它们将提供正确的行为。
它们的性能将比 copyupdate或新的拆包性能低得多,因为它们在更高的抽象级别上遍历每个键值对,但是它们确实尊重优先级的顺序(后者决定优先级)

您还可以在dict理解内手动将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(死神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


词典资源


My explanation of Python's dictionary implementation, updated for 3.6.
Answer on how to add new keys to a dictionary
Mapping two lists into a dictionary
官方Python docs on dictionaries
The Dictionary Even Mightier-Brandon Rhodes在Pycon 2017上的演讲
Modern Python Dictionaries, A Confluence of Great Ideas-Raymond Hettinger在Pycon 2017上的演讲

关于python - 如何在一个表达式中合并两个字典?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53048321/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com