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

标签 python dictionary merge

我有两个 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() 的最后一个胜利冲突处理也是我正在寻找的。)

最佳答案

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


对于词典 xy , z成为一个浅合并字典,其值来自 y替换来自 x 的那些.
  • 在 Python 3.9.0 或更高版本(2020 年 10 月 17 日发布)中:PEP-584 , discussed here , 已实现并提供了最简单的方法:
    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 keys and values of x
        z.update(y)    # modifies z with keys and values of y
        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 中提出和 available 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 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 中,您在内存中为每个 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(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 .
    字典旨在采用可散列的键(例如 frozenset 或元组),但 当键不是字符串时,此方法在 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(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-coming of dict. Nor is using the ** operator in this place an abuse of the mechanism, in fact, ** was designed precisely to pass dictionaries as keywords.


    同样,当键不是字符串时,它不适用于 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()) 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.


    是的。我必须让你回到这个问题,它要求对 进行浅合并。两个字典,第一个的值被第二个的值覆盖 - 在单个表达式中。
    假设有两个字典,一个可能会递归地将它们合并到一个函数中,但是您应该注意不要修改来自任一来源的字典,避免这种情况的最可靠方法是在赋值时进行复制。由于键必须是可散列的,因此通常是不可变的,复制它们是没有意义的:
    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" .
    性能较差但正确的 Ad-hoc
    这些方法的性能较低,但它们会提供正确的行为。
    它们的性能将远低于 copyupdate或新的解包,因为它们在更高的抽象级别遍历每个键值对,但它们确实尊重优先顺序(后一个字典具有优先级)
    您还可以在 dict comprehension 中手动链接字典。 :
    {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
    
    词典资源
  • 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
  • The official 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/38987/

    相关文章:

    python - 与类继承和 super().__init__ 作斗争

    python - 如何使用 sns.catplot 在箱线图上叠加散点图?

    python - bash 脚本中的 coreutils 超时对于应用程序不透明

    loops - range 子句针对 map 等不同的数据结构类型返回哪些不同的变量?

    dictionary - Golang 创建一片 map

    R:组合两个不等长的矩阵/向量,根据相同的值匹配行

    r - 取消列表 R

    SQL Server : MERGE performance

    python - 使用 MySQLdb 的嵌套查询

    python - 将包含一项的列表转换为字典值中的项本身