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

2024-04-27 03:04:13 发布

您现在位置:Python中文网/ 问答频道 /正文

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

(更清楚的是,最后一个赢得了dict.update()的冲突处理,这也是我想要的。)


Tags: 方法none字典表达式updatedictprint
3条回答

在您的情况下,您可以做的是:

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

这将根据您的需要,将最终dict放入z,并使键b的值被第二个(y)dict的值正确覆盖:

>>> 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)

How can I merge two Python dictionaries in a single expression?

对于字典xyz变成一个浅合并的字典,用y中的值替换x中的值。

  • 在Python3.5或更高版本中:

    z = {**x, **y}
    
  • 在Python2(或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,在未来的Python版本中,通过给dict一个合并运算符(应该是+)来进一步简化这个过程,它允许:

z = x + y                       # pseudocode for now...

但这还没有实施。

解释

假设你有两个听写,你想把它们合并成一个新的听写而不改变原来的听写:

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

所需的结果是得到一个合并了值的新字典(z),第二个dict的值将覆盖第一个dict的值。

>>> 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中,经典的Python方法是通过两个步骤来实现:

z = x.copy()
z.update(y) # which returns None since it mutates z

在这两种方法中,y将排在第二位,其值将替换x的值,因此'b'将指向最终结果中的3

在Python3.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中为所有dict工作。e、 g.给定指令ag

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

g中的键值对将优先于dictsaf等。

对其他答案的批评

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

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

在Python2中,为每个dict在内存中创建两个列表,在内存中创建第三个列表,其长度等于前两个组合在一起的长度,然后丢弃所有三个列表以创建dict。在Python3中,这将失败,因为您将两个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作为关键字参数传递给dict构造函数),否则很难读取,它不是预期的用法,因此它不是Pythonic。

这里有一个使用remediated in django的例子。

dict用于获取散列键s(例如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

mailing list中,语言的创造者Guido van Rossum写道:

I am fine with declaring dict({}, **{1:3}) illegal, since after all it is abuse of the ** mechanism.

以及

Apparently dict(x, **y) is going around as "cool hack" for "call x.update(y) and return x". Personally I find it more despicable than cool.

我的理解(以及对creator of the language的理解)是dict(**y)的预期用途是为了可读性而创建dict,例如:

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

而不是

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

对评论的回应

Despite what Guido says, dict(x, **y) is in line with the dict specification, which btw. works for both Python 2 and 3. The fact that this only works for string keys is a direct consequence of how keyword parameters work and not a short-comming of dict. Nor is using the ** operator in this place an abuse of the mechanism, in fact ** was designed precisely to pass dicts as keywords.

同样,当键是非字符串时,它对3也不起作用。隐式调用约定是,名称空间采用普通的dict,而用户只能传递作为字符串的关键字参数。所有其他可调用的都强制执行它。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()) is still the most readable solution for Python 2. Readability counts.

我的回答是:merge_two_dicts(x, y)如果我们真的关心可读性的话,实际上对我来说似乎要清楚得多。而且它也不是前向兼容的,因为Python 2越来越被弃用。

{**x, **y} does not seem to handle nested dictionaries. the contents of nested keys are simply overwritten, not merged [...] I ended up being burnt by these answers that do not merge recursively and I was surprised no one mentioned it. In my interpretation of the word "merging" these answers describe "updating one dict with another", and not merging.

是的。我必须让您回到这个问题,它要求两个字典的合并,第一个值在一个表达式中被第二个值覆盖。

假设有两个字典,其中一个可以递归地将它们合并到一个函数中,但是您应该注意不要修改来自任何一个源的dict,而最可靠的避免方法是在赋值时进行复制。由于密钥必须是散列的,因此通常是不可变的,因此复制它们是没有意义的:

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或新的解包要差得多,因为它们在更高的抽象级别上遍历每个键值对,但是它们尊重优先顺序(后面的指令具有优先权)

您还可以在听写理解中手动链接听写:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

或者在Python2.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

在Python3.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

词典资源

相关问题 更多 >