ChatGPT解决这个技术问题 Extra ChatGPT

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

我想将两个字典合并到一个新字典中。

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)

>>> z
{'a': 1, 'b': 3, 'c': 4}

每当两个字典中都存在键 k 时,只应保留值 y[k]


M
Mateen Ulhaq

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

对于字典 xy,它们的浅合并字典 zy 获取值,替换来自 x 的值。

在 Python 3.9.0 或更高版本(2020 年 10 月 17 日发布,PEP-584,在此讨论):z = x |是的

在 Python 3.5 或更高版本中:z = {**x, **y}

在 Python 2(或 3.4 或更低版本)中编写一个函数: def merge_two_dicts(x, y): z = x.copy() # 从 x 的键和值开始 z.update(y) # 用键和值修改 z y 返回 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 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 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()))。这是对资源和计算能力的浪费。

同样,当值是不可散列的对象(例如列表)时,在 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 的用法示例。

字典旨在采用可散列的键(例如 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"

性能较差但正确的 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

字典资源

我对 Python 字典实现的解释,更新为 3.6。

关于如何向字典添加新键的答案

将两个列表映射到字典中

字典上的官方 Python 文档

The Dictionary Even Mightier - Brandon Rhodes 在 Pycon 2017 上的演讲

现代 Python 词典,伟大思想的汇合 - Raymond Hettinger 在 Pycon 2017 上的演讲


@MohammadAzim“仅字符串”仅适用于可调用对象中的关键字参数扩展,不适用于通用解包语法。为了证明这有效:{**{(0, 1):2}} -> {(0, 1): 2}
当 PEP-0584 被接受时,这可能会改变。将使用以下语法实现新的联合运算符:x | y
@cal97g 是的,我在大约 10 天前的回答中提到了这个问题:stackoverflow.com/posts/26853961/revisions
你好,上面是一个总结,是的。由你决定。整件事将是一篇很棒的博客文章。注意 Py 3.4 及以下是 EOL,3.5 在 2020-09 接近 EOL。
我同意放弃旧方式的渴望,但有时人们不得不在只有旧技术可用的环境中工作。人们还必须更新代码,在新方法旁边看到旧方法可以让他们自信地用等效的新代码替换旧代码。我对重组材料的建议持开放态度,但我认为我们需要保留旧信息。
N
Nikita Vlasenko

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

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

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

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

如果您使用 Python 2,您甚至可以删除 list() 调用。要创建 z:

>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

如果你使用 Python 3.9.0a4 或更高版本,那么你可以直接使用:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)
{'a': 1, 'c': 11, 'b': 10}

不要使用它,因为它非常低效。 (请参阅下面的 timeit 结果。)如果包装函数不是一个选项,那么在 Py2 时代可能是必要的,但那些日子现在已经过去了。
M
Matthew Schinckel

替代:

z = x.copy()
z.update(y)

澄清为什么这不符合问题提供的标准:它不是一个单一的表达式,它不返回 z。
这么说吧:如果你需要用两行注释来解释你的一行代码给你把代码交给的人......你真的在一行中完成了吗? :) 我完全同意 Python 不适合这个:应该有一个更简单的方法。虽然这个答案更pythonic,但它真的那么明确或清楚吗? Update 不是人们经常使用的“核心”功能之一。
好吧,如果人们坚持让它成为一个单线,你总是可以做(lambda z: z.update(y) or z)(x.copy()):P
@AlexanderOh 我不确定这是否是个玩笑;我认为这是一个完美(有效)的答案! (至少就它的工作而言)但是当然;是的;第二条评论开创了先例!无论哪种方式;它是 Indeed pythonic!
@WilliamMartens 这不是玩笑。但是让我们面对现实吧,如果您针对单行表达式进行优化,那么您正在针对错误的事情进行优化。
C
Carl Meyer

另一个更简洁的选项:

z = dict(x, **y)

注意:这已成为一个流行的答案,但重要的是要指出,如果 y 有任何非字符串键,那么它完全有效的事实是对 CPython 实现细节的滥用,并且它在 Python 3、PyPy、IronPython 或 Jython 中不起作用。另外,Guido is not a fan。所以我不能推荐这种技术用于前向兼容或交叉实现的可移植代码,这实际上意味着应该完全避免它。


Works fine in Python 3 and PyPy and PyPy 3,无法与 Jython 或 Iron 对话。鉴于此模式是 explicitly documented(请参阅本文档中的第三个构造函数形式),我认为这不是“实现细节”,而是有意使用功能。
@amcgregor 您错过了关键短语“如果 y 有任何非字符串键”。这在 Python3 中是行不通的;它在 CPython 2 中工作的事实是一个不能依赖的实现细节。 IFF 你所有的键都保证是字符串,这是一个完全支持的选项。
t
twasbrillig

这可能不会是一个流行的答案,但你几乎肯定不想这样做。如果您想要一个合并的副本,请使用副本(或 deepcopy,具体取决于您想要什么)然后更新。这两行代码比使用 .items() + .items() 创建的单行代码更具可读性 - 更 Pythonic。显式优于隐式。

此外,当您使用 .items() (Python 3.0 之前的版本)时,您正在创建一个包含来自 dict 的项目的新列表。如果您的字典很大,那么开销会很大(两个大列表将在创建合并的字典后立即被丢弃)。 update() 可以更有效地工作,因为它可以逐项运行第二个字典。

time而言:

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO 前两者之间的微小减速对于可读性来说是值得的。此外,用于创建字典的关键字参数仅在 Python 2.3 中添加,而 copy() 和 update() 将在旧版本中工作。


t
the Tin Man

在后续回答中,您询问了这两种替代方案的相对性能:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

至少在我的机器上(运行 Python 2.5.2 的相当普通的 x86_64),替代方案 z2 不仅更短更简单,而且速度明显更快。您可以使用 Python 附带的 timeit 模块自行验证这一点。

示例 1:将 20 个连续整数映射到自身的相同字典:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2 以 3.5 倍左右获胜。不同的字典似乎产生了完全不同的结果,但 z2 似乎总是领先。 (如果 same 测试的结果不一致,请尝试使用大于默认值 3 的数字传入 -r。)

示例 2:非重叠字典将 252 个短字符串映射到整数,反之亦然:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 以大约 10 倍的优势获胜。在我看来,这是一个相当大的胜利!

在比较了这两者之后,我想知道 z1 的性能不佳是否可以归因于构建这两个项目列表的开销,这反过来又让我想知道这种变化是否会更好:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

一些快速测试,例如

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

让我得出结论,z3z1 快一些,但不如 z2 快。绝对不值得所有额外的打字。

这个讨论仍然缺少一些重要的东西,即这些替代方案与合并两个列表的“明显”方式的性能比较:使用 update 方法。为了尽量保持与表达式的平等地位,其中没有一个修改 x 或 y,我将制作 x 的副本而不是就地修改它,如下所示:

z0 = dict(x)
z0.update(y)

一个典型的结果:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

换句话说,z0z2 似乎具有基本相同的性能。你认为这可能是巧合吗?我不....

事实上,我什至声称纯 Python 代码不可能做得比这更好。而且,如果您可以在 C 扩展模块中做得更好,我想 Python 人员可能会对将您的代码(或您的方法的变体)合并到 Python 核心感兴趣。 Python 在很多地方都使用了 dict;优化其运营是一件大事。

你也可以这样写

z0 = x.copy()
z0.update(y)

正如托尼所做的那样,但(并不奇怪)符号的差异对性能没有任何可衡量的影响。使用任何适合您的方式。当然,他完全正确地指出,两语句版本更容易理解。


这在 Python 3 中不起作用; items() 不可连接,并且 iteritems 不存在。
R
Raymond Hettinger

在 Python 3.0 及更高版本中,您可以使用 collections.ChainMap 将多个 dicts 或其他映射组合在一起以创建单个可更新的视图:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(ChainMap({}, y, x))
>>> for k, v in z.items():
        print(k, '-->', v)
    
a --> 1
b --> 10
c --> 11

Python 3.5 及更高版本的更新:您可以使用PEP 448 扩展字典打包和解包。这既快速又简单:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}

Python 3.9 及更高版本的更新:您可以使用 PEP 584 联合运算符:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x | y
{'a': 1, 'b': 10, 'c': 11}

但是在使用 ChainMap 时应该小心,有一个问题是,如果您有重复的键,则使用第一个映射中的值,并且当您调用 del 时,说 ChainMap c 将删除该键的第一个映射。
@Prerit 您还期望它做什么?这是链式命名空间的正常工作方式。考虑 $PATH 在 bash 中的工作方式。删除路径上的可执行文件并不排除上游具有相同名称的另一个可执行文件。
@Raymond Hettinger 我同意,只是加了一点警告。大多数人可能不知道它。 :D
@Prerit您可以强制转换为 dict 以避免这种情况,即: dict(ChainMap({}, y, x))
建议的编辑队列已满,但有人将@wjandrea 的修改放入答案中,这是错误的 - 它不再是 a single, updateable view。应该还原此编辑。
R
Rainy

我想要类似的东西,但能够指定重复键上的值是如何合并的,所以我把它破解了(但没有对其进行大量测试)。显然这不是一个单一的表达式,而是一个单一的函数调用。

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

当不希望使用更短和更简单的解决方案(用第二个字典替换公共键的值)的默认行为时,方便的解决方案。对于 Python 3,iteritems() 在 dicts 中不再可用,可以简单地使用 items() 代替。
D
Dawid Gosławski

递归/深度更新字典

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

示范:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

输出:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

感谢 redna 的编辑。


这没有回答问题。该问题清楚地要求从原始字典 x 和 y 中获取新字典 z,其中 y 的值替换 x 的值 - 而不是更新的字典。此答案通过添加来自 x 的值来就地修改 y。更糟糕的是,它不会复制这些值,因此可以进一步修改修改后的字典 y,并且修改可以反映在字典 x 中。 @Jérôme 我希望这段代码不会对您的应用程序造成任何错误 - 至少考虑使用 deepcopy 来复制值。
@AaronHall 同意这不能回答问题。但它满足了我的需求。我理解这些限制,但这对我来说不是问题。想一想,也许这个名字会产生误导,因为它可能会唤起它不提供的 deepcopy。但它解决了深层嵌套问题。这是 Martellibot 的另一个实现:stackoverflow.com/questions/3232943/…
B
Bilal Syed Hussain

Python 3.5 (PEP 448) 允许更好的语法选项:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

甚至

final = {'a': 1, 'b': 1, **x, **y}

在 Python 3.9 中,您还可以使用 |和 |= 使用 PEP 584 中的以下示例

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

该解决方案在哪些方面优于 dict(x, **y) 解决方案?正如您 (@CarlMeyer) 在您自己的答案 (stackoverflow.com/a/39858/2798610) 注释中提到的那样,Guido 认为该解决方案 非法
Guido 不喜欢 dict(x, **y) 的(非常好的)原因是它依赖于 y 仅具有有效关键字参数名称的键(除非您使用 CPython 2.7,其中 dict 构造函数作弊)。此反对/限制不适用于 PEP 448,它将 ** 解包语法推广到 dict 文字。所以这个解决方案和dict(x, **y)一样简洁,没有缺点。
d
driax

在不使用副本的情况下,我能想到的最佳版本是:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

它比 dict(x.items() + y.items()) 快,但不如 n = copy(a); n.update(b) 快,至少在 CPython 上是这样。如果您将 iteritems() 更改为 items(),此版本也适用于 Python 3,这是由 2to3 工具自动完成的。

就我个人而言,我最喜欢这个版本,因为它用一种功能语法很好地描述了我想要的东西。唯一的小问题是 y 的值优先于 x 的值并没有完全明显,但我认为这并不难弄清楚。


G
Greg Hewgill
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

对于在两个字典 ('b') 中都有键的项目,您可以通过将其放在最后来控制哪个最终出现在输出中。


在 python 3 中你会得到 TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items' ...你应该用 list() 封装每个 dict 像: dict(list(x.items()) + list (y.items()))
@justSaid itertools.chain(x.items(), y.items()) 也可以使用。
p
phobie

虽然这个问题已经回答了好几次,但这个问题的简单解决方案尚未列出。

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

它与上面提到的 z0 和邪恶的 z2 一样快,但易于理解和更改。


但它是三个语句而不是一个表达式
是的!提到的单一表达式解决方案要么缓慢要么邪恶。好的代码是可读和可维护的。所以问题是问题而不是答案。我们应该寻求问题的最佳解决方案,而不是单线解决方案。
去掉 z4 = {} 并将下一行更改为 z4 = x.copy() —— 好于只有好的代码不会做不必要的事情(这使得它更具可读性和可维护性)。
您的建议会将其更改为 Matthews 的答案。虽然他的回答很好,但我认为我的答案更具可读性和可维护性。额外的行只有在花费执行时间时才会是坏的。
我建议你把它放到一个函数中
S
Sam Watkins
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

在这些阴暗和可疑的答案中,这个光辉的例子是在 Python 中合并 dicts 的唯一好方法,得到终身独裁者 Guido van Rossum 本人的认可!其他人建议了其中的一半,但没有将其放入函数中。

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

给出:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}

E
EMS

如果你认为 lambdas 是邪恶的,那就不要再读下去了。根据要求,您可以使用一个表达式编写快速且节省内存的解决方案:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

如上所述,使用两行代码或编写一个函数可能是更好的方法。


R
Robino

是pythonic。使用 comprehension

z={k: v for d in [x,y] for k, v in d.items()}

>>> print z
{'a': 1, 'c': 11, 'b': 10}

C
Community

在 python3 中,items 方法 no longer returns a list,而是一个 view,它的作用类似于一个集合。在这种情况下,您需要使用集合并集,因为与 + 连接不起作用:

dict(x.items() | y.items())

对于 2.7 版中类似 python3 的行为,viewitems 方法应代替 items

dict(x.viewitems() | y.viewitems())

无论如何,我更喜欢这种表示法,因为将其视为集合联合操作而不是串联似乎更自然(如标题所示)。

编辑:

关于 python 3 的更多要点。首先,请注意 dict(x, **y) 技巧在 python 3 中不起作用,除非 y 中的键是字符串。

此外,Raymond Hettinger 的 Chainmap answer 非常优雅,因为它可以将任意数量的 dicts 作为参数,但 from the docs 它看起来像是按顺序查看每个查找的所有 dicts 列表:

查找连续搜索底层映射,直到找到一个键。

如果您的应用程序中有很多查找,这可能会减慢您的速度:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

因此,查找速度要慢一个数量级。我是 Chainmap 的粉丝,但在可能有很多查找的地方看起来不太实用。


N
Nico Schlömer

我用 perfplot 对建议进行了基准测试,发现好的旧的

temp = x.copy()
temp.update(y)

与新的(Python 3.9+)一起是最快的解决方案

x | y

https://i.stack.imgur.com/z8pG1.png

重现情节的代码:

from collections import ChainMap
from itertools import chain
import perfplot


def setup(n):
    x = dict(zip(range(n), range(n)))
    y = dict(zip(range(n, 2 * n), range(n, 2 * n)))
    return x, y


def copy_update(data):
    x, y = data
    temp = x.copy()
    temp.update(y)
    return temp


def add_items(data):
    x, y = data
    return dict(list(x.items()) + list(y.items()))


def curly_star(data):
    x, y = data
    return {**x, **y}


def chain_map(data):
    x, y = data
    return dict(ChainMap({}, y, x))


def itertools_chain(data):
    x, y = data
    return dict(chain(x.items(), y.items()))


def python39_concat(data):
    x, y = data
    return x | y


b = perfplot.bench(
    setup=setup,
    kernels=[
        copy_update,
        add_items,
        curly_star,
        chain_map,
        itertools_chain,
        python39_concat,
    ],
    labels=[
        "copy_update",
        "dict(list(x.items()) + list(y.items()))",
        "{**x, **y}",
        "chain_map",
        "itertools.chain",
        "x | y",
    ],
    n_range=[2 ** k for k in range(18)],
    xlabel="len(x), len(y)",
    equality_check=None,
)
b.save("out.png")
b.show()

M
Mathieu Larose

两本词典

def union2(dict1, dict2):
    return dict(list(dict1.items()) + list(dict2.items()))

词典

def union(*dicts):
    return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))

sum 表现不佳。请参阅https://mathieularose.com/how-not-to-flatten-a-list-of-lists-in-python/


r
reubano

使用保留顺序的 itertools 的简单解决方案(后面的 dicts 优先)

# py2
from itertools import chain, imap
merge = lambda *args: dict(chain.from_iterable(imap(dict.iteritems, args)))

# py3
from itertools import chain
merge = lambda *args: dict(chain.from_iterable(map(dict.items, args)))

它的用法:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}

C
Community

导致 Matthew's answer 的单一表达式解决方案的滥用:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

你说你想要一个表达式,所以我滥用 lambda 来绑定一个名称,并使用元组来覆盖 lambda 的一个表达式限制。随意畏缩。

如果您不关心复制它,您当然也可以这样做:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}

g
gilch

如果您不介意改变 x

x.update(y) or x

简单、易读、高性能。你知道 update() 总是返回 None,这是一个假值。所以上面的表达式在更新后总是会计算为 x

标准库中的大多数变异方法(如 .update())按约定返回 None,因此这种模式也适用于这些方法。但是,如果您使用 dict 子类或其他不遵循此约定的方法,则 or 可能会返回其左操作数,这可能不是您想要的。相反,您可以使用元组显示和索引,无论第一个元素的计算结果如何(尽管它不是很漂亮),它都可以工作:

(x.update(y), x)[-1]

如果您的变量中还没有 x,您可以使用 lambda 来创建局部变量,而无需使用赋值语句。这相当于使用 lambda 作为 let 表达式,这是函数式语言中的常用技术,但可能不是 Python 的。

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

虽然它与以下使用 new walrus 运算符(仅限 Python 3.8+)没有什么不同,

(x := {'a': 1, 'b': 2}).update(y) or x

特别是如果您使用默认参数:

(lambda x={'a': 1, 'b': 2}: x.update(y) or x)()

如果您确实想要一份副本,PEP 584 样式 x | y 是 3.9+ 上最 Pythonic 的。如果您必须支持旧版本,则 PEP 448 样式 {**x, **y} 对于 3.5+ 最简单。但是,如果在您的(甚至更旧的)Python 版本中不可用,let 表达式 模式也可以在这里使用。

(lambda z=x.copy(): z.update(y) or z)()

(当然,这几乎等同于 (z := x.copy()).update(y) or z,但如果您的 Python 版本足够新,那么 PEP 448 样式将可用。)


x
xjcl

Python 3.9 中的New 使用联合运算符 (|) 合并类似于 setdict

>>> d = {'a': 1, 'b': 2}
>>> e = {'a': 9, 'c': 3}
>>> d | e
{'a': 9, 'b': 2, 'c': 3}

对于匹配键,dict 优先

这也适用于 |= 就地修改 dict

>>> e |= d    # e = e | d
>>> e
{'a': 1, 'c': 3, 'b': 2}

这增加了几个月前没有提到的内容? stackoverflow.com/a/61116810/674039
B
Bijou Trouvaille

借鉴这里和其他地方的想法,我理解了一个功能:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

用法(在 python 3 中测试):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

您可以改用 lambda。


G
GetFree

太傻了,.update 什么都不返回。
我只是使用一个简单的辅助函数来解决问题:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

例子:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

k
kjo

(仅适用于 Python2.7*;Python3* 有更简单的解决方案。)

如果您不反对导入标准库模块,您可以这样做

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

lambda 中的 or a 位是必需的,因为 dict.update 总是在成功时返回 None。)


u
upandacross

迄今为止列出的解决方案的问题是,在合并字典中,键“b”的值是 10,但根据我的思维方式,它应该是 12。鉴于此,我提出以下内容:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

结果:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}

您可能对 cytoolz.merge_with (toolz.readthedocs.io/en/latest/…) 感兴趣
S
ShadowRanger

在 Python 3.8 发布 (scheduled for 20 October, 2019) 时将有一个新选项,这要感谢 PEP 572: Assignment Expressions。新的赋值表达式运算符 := 允许您分配 copy 的结果并仍然使用它来调用 update,从而使组合代码成为一个表达式,而不是两个语句,改变:

newdict = dict1.copy()
newdict.update(dict2)

至:

(newdict := dict1.copy()).update(dict2)

同时在各方面表现相同。如果您还必须返回结果 dict(您要求返回 dict 的表达式;上面创建并分配给 newdict,但不返回它,因此您不能使用它来传递参数按原样添加到函数,a la myfunc((newdict := dict1.copy()).update(dict2))),然后只需将 or newdict 添加到末尾(因为 update 返回 None,这是错误的,然后它将评估并返回 newdict 作为表达):

(newdict := dict1.copy()).update(dict2) or newdict

重要警告:总的来说,我不鼓励这种方法,而是支持:

newdict = {**dict1, **dict2}

解包方法更清晰(对于首先了解广义解包的人来说,which you should),根本不需要结果的名称(因此在构造一个立即传递给函数或包含在 list/tuple 文字等中),并且几乎可以肯定也更快,(在 CPython 上)大致相当于:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

但在 C 层使用具体的 dict API 完成,因此不涉及动态方法查找/绑定或函数调用调度开销(其中 (newdict := dict1.copy()).update(dict2) 在行为上不可避免地与原始的两行代码相同,在离散步骤,动态查找/绑定/调用方法。

它也更具可扩展性,因为合并三个 dict 很明显:

 newdict = {**dict1, **dict2, **dict3}

使用赋值表达式不会像那样缩放;你能得到的最接近的是:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

或者没有 None 的临时元组,但对每个 None 结果进行真实性测试:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

其中任何一个显然都更丑陋,并且还包括进一步的低效率(要么浪费了 None 的临时 tuple 用于逗号分隔,要么对每个 updateNone 返回用于 or 分隔的无意义的真实性测试) .

赋值表达式方法的唯一真正优势出现在以下情况:

您有需要处理集合和字典的通用代码(它们都支持复制和更新,因此代码大致按照您的预期工作)您希望接收任意类似字典的对象,而不仅仅是字典本身,并且必须保留左侧的类型和语义(而不是以普通的 dict 结尾)。虽然 myspecialdict({**speciala, **specialb}) 可能会起作用,但它会涉及一个额外的临时 dict,并且如果 myspecialdict 具有普通 dict 无法保留的功能(例如,常规 dicts 现在会根据键的第一次出现来保留顺序,以及基于键的最后出现的值;您可能想要一个基于键的最后出现来保留顺序的键,因此更新值也会将其移至末尾),那么语义将是错误的。由于赋值表达式版本使用命名方法(可能被重载以适当地表现),它根本不会创建一个 dict(除非 dict1 已经是一个 dict),保留原始类型(和原始类型的语义),同时避免任何临时工。


r
reetesh11
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

这应该可以解决您的问题。


我会推荐使用计数器的 .update() 而不是 +。这是因为,如果任何键的总和结果为 0,Counter 将删除它。
R
RemcoGerlich

这可以通过单个 dict 理解来完成:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

在我看来,“单一表达式”部分的最佳答案是不需要额外的功能,而且很短。


我怀疑性能不会很好;从每个 dict 中创建一个集合,然后只遍历键意味着每次都再次查找该值(虽然相对较快,但仍会增加函数的缩放顺序)
这一切都取决于我们使用的 python 版本。在 3.5 及以上版本中 {**x,**y} 给出了连接字典