热门标签:
Q:

如何在单个表达式中合并两个字典(取字典的联合)?

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

原网址
A:

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

对于字典xyz变为浅合并的字典,其值来自y替换来自x的值。

  • 在Python3.9.0或更高版本(2020年10月17日发布)中:PEP-584这里讨论的,被实现并提供了最简单的方法:

    z = x | y          # NOTE: 3.9+ ONLY
    
  • 在Python3.5或更高版本中:

    z = {**x, **y}
    
  • 在Python2中,(或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}

PEP448中提出的一个新的语法是

z = {**x, **y}

它确实是一个单一的表达。

请注意,我们也可以与字面符号合并:

z = {**x, 'foo': 1, 'bar': 2, **y}

而现在:

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

它现在显示为3.5的发布时间表中实现的,PEP478,它现在已经进入了Python3.5文档中的新功能。

但是,由于许多组织仍在使用Python2,因此您可能希望以向后兼容的方式执行此操作。 在Python2和Python3.0-3.4中可用的经典Pythonic方法是作为两步过程执行此操作:

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

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

尚未在Python3.5上,但想要一个单个表达式

如果您还没有使用Python3.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

此函数将在Python2和3中适用于所有字典。 例如给定的字典ag:

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

和键值对将优先于字典af,依此类推。

对其他答案的批评

不要使用您在以前接受的答案中看到的内容:

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()))。 这是对资源和计算能力的浪费。

同样,在Python3中取items()的并集(在Python2.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。

下面是在django中修复用法的一个例子。

字典旨在采用可哈希键(例如frozensets或元组),但当键不是字符串时,此方法在Python3中失败。

>>> 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.更新(y)并返回x"。 就个人而言,我觉得它比 酷。

我的理解(以及对语言的创建者的理解)是dict(**y)的预期用法是为可读性目的创建字典,例如:

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

而不是

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

回应意见

尽管Guido说,dict(x, **y)符合dict规范,顺便说一句。 适用于Python2和3。 这只适用于字符串键的事实是关键字参数如何工作的直接结果,而不是dict的短期结果。 在这个地方使用**运算符也不是滥用机制,事实上,**的设计正是为了将字典作为关键字传递。

再次,当键不是字符串时,它不适用于3。 隐式调用约定是命名空间采用普通字典,而用户只能传递字符串的关键字参数。 所有其他callable强制执行它。 dict在Python2中打破了这种一致性:

>>> 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),这种不一致是不好的。 因此,它在Python3中得到了修复,因为这种用法可能是一个突破性的变化。

我向你提出,故意编写仅在一种语言的一个版本中工作或仅在某些任意约束下工作的代码是恶意的无能。

更多评论:

dict(x.items() + y.items())仍然是Python2最具可读性的解决方案。 可读性很重要。

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

{**x, **y}似乎不处理嵌套字典。 嵌套键的内容只是被复盖,而不是合并[。..]我最终被这些不递归合并的答案所烧毁,我很惊讶没有人提到它。 在我对"合并"这个词的解释中,这些答案描述了"用另一个dict更新一个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: {}}}

提出其他值类型的意外情况远远超出了这个问题的范围,所以我将向您指出我对"字典合并字典"的规范问题的回答。

性能较差但正确的Ad-hoc

这些方法的性能较低,但它们会提供正确的行为。 它们比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()) # 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())))

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

字典资源

所有回答

共 29 条

author avatar

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

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

这将根据您的需要,将最终的dict放入z中,并使keyb的值被第二个(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}

如果您使用Python2,您甚至可以删除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}
author avatar

另一种选择:

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

另一个更简洁的选择:

z = dict(x, **y)

注意:这已经成为一个流行的答案,但重要的是要指出,如果y有任何非字符串键,那么这根本有效的事实是滥用CPython实现细节,它在Python3中不起作用, 此外,Guido不是粉丝。 所以我不能推荐这种技术用于向前兼容或交叉实现的可移植代码,这真的意味着应该完全避免。

author avatar

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

此外,当您使用。items()(python3.0之前),您正在创建一个包含dict中的项目的新列表。 如果你的字典很大,那么这是相当多的开销(两个大的列表,一旦创建合并的字典就会被扔掉)。 update()可以更有效地工作,因为它可以逐项运行第二个dict。

时间方面

>>> 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前两者之间的微小减速对于可读性来说是值得的。 此外,字典创建的关键字参数仅在Python2.3中添加,而copy()和update()将在旧版本中工作。

author avatar

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

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

在我的机器上,至少(运行Python2.5.2的相当普通的x86_64),alternativez2不仅更短,更简单,而且显着更快。 您可以使用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)

正如Tony所做的那样,但(毫不奇怪)符号的差异对性能没有任何可衡量的影响。 使用任何看起来对你合适的。 当然,他指出双语句版本更容易理解是绝对正确的。

author avatar

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

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

Python3.5及更高版本的更新:您可以使用PEP448扩展字典打包和解包。 这是快速和容易的:

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

Python3.9及更高版本的更新:您可以使用PEP584union运算符:

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

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

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
author avatar

递归/深度更新字典

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

感谢rednaw的编辑。

author avatar

Python3.5(PEP448)允许更好的语法选项:

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}

在Python3.9中,您还可以使用|和|=与PEP584中的以下示例一起使用

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

我能想到的最好的版本,而不使用复制将是:

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(),则此版本也适用于Python3,这是由2to3工具自动完成的。

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

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

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

author avatar

虽然这个问题已经被回答了好几次, 这个简单的解决问题的方法还没有列出。

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

它和上面提到的z0和邪恶的z2一样快,但很容易理解和改变。

author avatar
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'}
author avatar

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

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}

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

author avatar

是pythonic。 使用a理解:

z={i:d[i] for d in [x,y] for i in d}

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

在python3中,items方法不再返回列表,而是返回一个视图,它的作用就像一个集合。 在这种情况下,您需要使用集合联合,因为与+连接将不起作用:

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

对于2.7版本中类似python3的行为,viewitems方法应该代替items工作:

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

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

编辑:

python3还有几个要点。 首先,请注意,dict(x, **y)技巧在python3中不起作用,除非y中的键是字符串。

此外,Raymond Hettinger的Chainmapanswer非常优雅,因为它可以将任意数量的dicts作为参数,但是从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的粉丝,但在可能有很多查找的地方看起来不那么实用。

author avatar

两本词典

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

n字典

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/

author avatar

使用保留顺序的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}
author avatar

滥用导致Matthew的答案的单表达式解决方案

>>> 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}
author avatar

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

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

与新的(Python3.9+)一起是最快的解决方案

x | y


代码重现剧情:

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()
author avatar

尽管这个shallow字典的答案很好,但这里定义的方法实际上都没有做深度字典合并。

例子如下:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

人们会期待这样的结果:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

相反,我们得到这个:

{'two': True, 'one': {'extra': False}}

'One'条目应该有'depth_2'和'extra'作为字典中的项目,如果它真的是一个合并。

使用链也不起作用:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

结果:

{'two': True, 'one': {'extra': False}}

Rcwesick给出的深度合并也会产生相同的结果。

是的,合并示例字典将起作用,但它们都不是合并的通用机制。 一旦我编写了一个执行真正合并的方法,我将稍后更新此内容。

author avatar

如果你不介意变异x

x.update(y) or x

简单,可读,高性能。 Youknowupdate()总是返回None,这是一个假值。 所以上面的表达式在更新后总是会求值为x

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

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

如果变量中还没有x,则可以使用lambda创建本地,而无需使用赋值语句。 这相当于使用lambda作为let表达式,这是函数式语言中的常用技术,但可能是非节奏的。

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

虽然它与以下使用新的walrus运算符(仅Python3.8+)没有那么大的不同,

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

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

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

如果你确实想要一个副本,PEP584stylex | y是3.9+上最Pythonic的。 如果您必须支持旧版本,PEP448style{**x, **y}对于3.5+最简单。 但是,如果这在你的(甚至更旧的)Python版本中不可用,那么let expression模式也在这里工作。

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

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

author avatar

New在Python3.9中:使用union运算符(|)合并dicts类似于sets:

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

对于匹配键,rightdict优先

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

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

太傻了,.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
author avatar

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

如果您不反对导入标准库模块,则可以执行以下操作

from functools import reduce

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

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

author avatar

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

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

用法(在python3中测试):

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来代替。

author avatar

到目前为止,我列出的解决方案的问题是,在合并的字典中,键"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}
author avatar

当Python3.8发布(定于2019年10月20日)时,将有一个新选项,这要归功于PEP572:赋值表达式。 新的赋值表达式运算符:=允许您分配copy的结果,并仍然使用它来调用update,将组合的代码保留为单个表达式,而不是两个语句,更改:

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

到:

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

在各方面表现都是一样的。 如果你还必须返回结果dict(你要求一个返回dict的表达式;上面创建并分配给newdict,但没有返回它,所以你不能用它来传递一个参数给一个函数,一个lamyfunc((newdict := dict1.copy()).update(dict2))),那么只需在最后添加or newdict(因为update返回None,这是虚假的,它将评估并返回newdict作为表达式的结果):

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

重要警告:一般来说,我不鼓励这种方法,有利于:

newdict = {**dict1, **dict2}

解包方法更清晰(对于任何一个知道广义解包的人来说,你应该),根本不需要结果的名称(所以当构造一个立即传递给函数或包含在list/None

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

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

它也更具可扩展性,因为合并三个dicts是显而易见的:

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

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

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

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

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

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

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

  1. 你有需要同时处理sets和dicts的泛型代码(它们都支持copyupdate,所以代码的工作原理大致如你所期望的那样)
  2. 您希望接收任意的类似dict的对象,而不仅仅是dict本身,并且必须保留左侧的类型和语义(而不是以普通dict结束)。 虽然myspecialdict({**speciala, **specialb})可能起作用,但它会涉及一个额外的临时dict,如果myspecialdict具有普通dict无法保留的功能(例如,常规dict现在根据键的第一次出现保留顺序,而值根据键的最后一次出现保留顺序;您可能需要一个基于键的最后外观保留顺序,因此更新值也会将其移动到最后),那么语义就会是错误的。 由于赋值表达式版本使用命名方法(这些方法可能被重载以适当地表现),它根本不会创建一个dict(除非dict1已经是一个dict),保留原始类型(和原始类型的语义),同时避免任何临时性。
author avatar
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

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

相似问题