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

Carl Meyer 4349 python merge dictionary

我有两个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.update()也是我正在寻找的.)

Aaron Hall.. 5044

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

对于字典xy,z成为浅合并字典,从值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)
    

说明

假设您有两个dicts,并且您希望将它们合并到一个新的dict而不更改原始的dicts:

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

期望的结果是获得一个新的字典(z),其值合并,第二个字典的值覆盖第一个.

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

PEP 448中提出并且从Python 3.5开始提供的新语法是

z = {**x, **y}

它确实是一个表达式.

请注意,我们也可以使用文字符号合并:

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

现在:

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

它现在显示为在3.5,PEP 478发布时间表中实现,现在它已经进入了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)

您还可以创建一个函数来合并未定义数量的dicts,从零到非常大的数字:

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

对于所有dicts,此函数将在Python 2和3中使用.例如给出的a决定g:

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

类似地,当值是不可用的对象(例如列表)时,items()采用Python 3中的联合(viewitems()在Python 2.7中)也将失败.即使您的值是可清除的,因为集合在语义上是无序的,所以行为在优先级方面是未定义的.所以不要这样做:

>>> 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修复的用法示例.

Dicts旨在获取可散列密钥(例如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

邮件列表中,该语言的创建者Guido van Rossum写道:

我很好地宣布dict({},**{1:3})是非法的,因为它毕竟是滥用**机制.

显然dict(x,**y)作为"调用x.update(y)并返回x"的"酷黑客".就个人而言,我发现它比酷酷更卑鄙.

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

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

代替

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

对评论的回应

尽管Guido说,dict(x, **y)这符合dict规范,顺便说一下.适用于Python 2和3.事实上,这仅适用于字符串键,这是关键字参数如何工作而不是dict短路的直接结果.在这个地方也没有使用**运算符滥用该机制,事实上**被精确地设计为将dicts作为关键字传递.

同样,当键是非字符串时,它不适用于3.隐式调用契约是命名空间采用普通的dicts,而用户只能传递字符串的关键字参数.所有其他callables强制执行它.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}似乎没有处理嵌套字典.嵌套项的内容只是覆盖,不合并[...]我结束了这些答案不递归合并被烧毁,我很惊讶没有人提到它.在我对"合并"一词的解释中,这些答案描述了"用另一个更新一个字典",而不是合并.

是.我必须回过头来回答这个问题,即要求两个词典的浅层合并,第一个的值被第二个词覆盖 - 在一个表达式中.

假设有两个词典字典,一个可以在一个函数中递归地合并它们,但是你应该注意不要从任何一个源修改dicts,并且最可靠的方法是在分配值时复制它们.由于密钥必须是可清洗的,因此通常是不可变的,因此复制它们是没有意义的:

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或新的拆包,因为他们遍历在更高的抽象水平的每个键-值对,但他们做的尊重优先顺序(后者类型的字典具有优先权)

你也可以在dict理解中手动链接dicts:

{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(deadsnakes 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

字典资源

  • @MohammadAzim“仅字符串”仅适用于可调用项中的关键字参数扩展,不适用于广义的解包语法。为了证明这是可行的:`{** {(0,1):2}}`-&gt;`{(0,1):2}` (5认同)

Thomas Vande.. 1563

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

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

这将根据您的需要放入最终的dict z,并使key的值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}


Matthew Schi.. 610

替代:

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

  • 澄清为什么这不符合问题提供的标准:它不是单个表达式而且它不返回z. (73认同)
  • 这样说吧:如果你需要将两行注释解释为一行代码,那么你可以将代码交给人们......你真的在一行中完成了吗?:)我完全同意Python对此不好:应该有一个更简单的方法.虽然这个答案更加抒情,但它真的是明确的还是明确的?"更新"不是人们倾向于大量使用的"核心"功能之一. (10认同)

Carl Meyer.. 320

另一个更简洁的选择:

z = dict(x, **y)

注意:这已成为一个流行的答案,但重要的是要指出,如果y有任何非字符串键,这一点的工作原理是滥用CPython实现细节,它在Python 3中不起作用,或者在PyPy,IronPython或Jython中.此外,Guido不是粉丝.所以我不能推荐这种技术用于前向兼容或交叉实现的可移植代码,这实际上意味着它应该完全避免.

  • @amcgregor您错过了“如果y有任何非字符串键”的关键词。那是在Python3中行不通的;它在CPython 2中起作用的事实是不能依赖的实现细节。如果所有键都保证为字符串,则这是一个完全受支持的选项。 (2认同)

Tony Meyer.. 198

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

此外,当您使用.items()(Python 3.0之前)时,您正在创建一个包含dict项目的新列表.如果你的词典很大,那么这就是很多开销(一旦创建合并的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前两者之间的微小减速对于可读性来说是值得的.此外,字典创建的关键字参数仅在Python 2.3中添加,而copy()和update()将在旧版本中使用.


zaphod.. 138

在后续回答中,您询问了这两种备选方案的相对表现:

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

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

示例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似乎总是提前出现.(如果同一测试的结果不一致,请尝试-r使用大于默认值3的数字传入.)

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

让我得出的结论是,z3它有点快z1,但并不快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

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

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

你也可以这样写

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

正如Tony所做的那样,但(并不奇怪)表示法中的差异结果表明不会对性能产生任何可衡量的影响.使用适合您的任何一种.当然,他指出双语句版本更容易理解是完全正确的.

  • 这在Python 3中不起作用; `items()`不可连接,`iteritems`不存在. (4认同)

rcreswick.. 104

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

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


Raymond Hett.. 103

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

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

a --> 1
b --> 10
c --> 11

  • @Prerit你还期望它做什么?这是链接名称空间工作的正常方式.考虑$ PATH在bash中的工作原理.删除路径上的可执行文件并不排除在上游具有相同名称的另一个可执行文件. (6认同)
  • 但是在使用ChainMap时应该小心谨慎,如果你有重复键,那么第一个映射的值就会被使用,当你调用`del`时,如果一个ChainMap c将删除该键的第一个映射. (3认同)
  • @Raymond Hettinger我同意,只是增加了警告。大多数人可能对此一无所知。:D (2认同)

Stan.. 80

递归/深度更新字典

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的编辑.


driax.. 66

我不能使用副本时可以想到的最佳版本是:

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

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

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


Bilal Syed H.. 57

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}

  • Guido不喜欢`dict(x,**y)`因为(非常好)的原因,它依赖于`y`只有有效关键字参数名的键(除非你使用的是CPython 2.7,其中dict构造函数作弊).该异议/限制不适用于PEP 448,它将`**`解包语法概括为dict文字.所以这个解决方案与`dict(x,**y)`具有相同的简洁性,没有缺点. (13认同)

Greg Hewgill.. 56

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

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


phobie.. 46

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

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

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

  • 是! 提到的一个表达式解决方案要么是缓慢的,要么是邪恶的.好的代码是可读和可维护的.所以问题是问题不是答案.我们应该问一个问题的最佳解决方案,而不是单线解决方案. (14认同)
  • 丢失`z4 = {}`并将下一行更改为`z4 = x.copy()` - 更好的代码不会做不必要的事情(这使得它更具可读性和可维护性). (6认同)
  • 但这是三个陈述而不是一个表达 (2认同)
  • 你的建议会改变马修斯的答案.虽然他的答案很好,但我认为我的答案更具可读性和更好的可维护性.如果花费执行时间,额外的行只会是坏的. (2认同)

Sam Watkins.. 45

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


EMS.. 36

如果你认为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}

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


Robino.. 30

是pythonic.使用理解:

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

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


beardc.. 28

在python3中,该items方法不再返回列表,而是返回一个视图,它就像一个集合.在这种情况下,你需要采用set union,因为连接+将不起作用:

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

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

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

不管怎样我更喜欢这种表示法,因为把它想象成一个联合操作而不是连接似乎更自然(如标题所示).

编辑:

python 3还有几点.首先,请注意,dict(x, **y)除非键y是字符串,否则这个技巧在python 3中不起作用.

此外,Raymond Hettinger的Chainmap 答案非常优雅,因为它可以使用任意数量的dicts作为参数,但是从文档看起来它依次查看每个查找的所有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的粉丝,但在可能有很多查找的地方看起来不那么实用.


Claudiu.. 21

滥用导致马修答案的单表达式解决方案:

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


Mathieu Laro.. 20

两本词典

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/


reubano.. 20

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

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, 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}


小智.. 15

尽管这个浅层词典的答案很好,但这里定义的方法实际上并没有进行深层词典合并.

示例如下:

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给出的深度合并也会产生相同的结果.

是的,它可以合并样本字典,但它们都不是合并的通用机制.一旦我编写了一个执行真正合并的方法,我将在稍后更新.


Bijou Trouva.. 10

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

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代替.


小智.. 10

我在今天列出的解决方案中遇到的问题是,在合并的字典中,键"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}


reetesh11.. 10

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

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


RemcoGerlich.. 9

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

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

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

  • 这一切都取决于我们正在使用的python的版本.在3.5及以上{**x,**y}给出了连接字典 (2认同)

kjo.. 9

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

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

from functools import reduce

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

(这里的or alambda是必要的,因为dict.update总是会None在成功时返回.)


John La Rooy.. 8

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


GetFree.. 7

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


gilch.. 6

如果您不介意变异x

x.update(y) or x

简单,可读,高效。您知道 update()总是会返回None,这是一个错误的值。因此它将始终评估为x

按照约定update返回标准库中的方法,例如,None按约定返回,因此该技巧也适用于那些方法。

如果您使用的库不遵循此约定,则可以使用元组显示和索引使它成为单个表达式,而不是or,但是可读性不强。

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

如果还没有x变量,则可以使用lambda本地变量而不使用赋值语句。这相当于lambda用作let表达式,这是功能语言中的一种常用技术,但是有点不合逻辑。

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

如果您确实想要副本,则最好使用PEP 448 {**x, **y}。但是,如果这是不可用的,作品也在这里。

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


kiriloff.. 5

使用词典理解,你可以

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

dc = {xi:(x[xi] if xi not in list(y.keys()) 
           else y[xi]) for xi in list(x.keys())+(list(y.keys()))}

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

请注意if else理解的语法

{ (some_key if condition else default_key):(something_if_true if condition 
          else something_if_false) for key, value in dict_.items() }

  • 我喜欢使用字典理解的想法,但你的实现很弱.在列表中使用`...(y.keys())`而不仅仅是`...在y`中是疯狂的. (8认同)

Alfe.. 5

我知道这确实不适合问题的具体内容(“一个班轮”),但是由于上面的所有答案都没有朝这个方向发展,而很多答案都涉及性能问题,所以我觉得我应该贡献自己的思想。

根据用例,可能不必创建给定输入字典的“真实”合并字典。在许多情况下,执行此操作的视图可能就足够了,即,合并字典一样工作的对象将不会完全计算它。可以这么说,这是合并字典的一种惰性版本。

在Python中,这非常简单,可以使用我文章结尾处显示的代码来完成。鉴于此,原始问题的答案将是:

z = MergeDict(x, y)

使用此新对象时,它的行为类似于合并的字典,但具有不变的创建时间和不变的内存占用,同时保持原始字典不变。创建它比其他建议的解决方案便宜。

当然,如果您大量使用结果,那么您将在某个时候达到极限,在该极限下,创建真正的合并字典将是更快的解决方案。如我所说,这取决于您的用例。

如果您觉得自己想合并一个真正的merge dict,那么调用dict(z)会产生它(但是当然比其他解决方案要贵得多,所以值得一提)。

您还可以使用此类创建一种写时复制字典:

a = { 'x': 3, 'y': 4 }
b = MergeDict(a)  # we merge just one dict
b['x'] = 5
print b  # will print {'x': 5, 'y': 4}
print a  # will print {'y': 4, 'x': 3}

这是的简单代码MergeDict

class MergeDict(object):
  def __init__(self, *originals):
    self.originals = ({},) + originals[::-1]  # reversed

  def __getitem__(self, key):
    for original in self.originals:
      try:
        return original[key]
      except KeyError:
        pass
    raise KeyError(key)

  def __setitem__(self, key, value):
    self.originals[0][key] = value

  def __iter__(self):
    return iter(self.keys())

  def __repr__(self):
    return '%s(%s)' % (
      self.__class__.__name__,
      ', '.join(repr(original)
          for original in reversed(self.originals)))

  def __str__(self):
    return '{%s}' % ', '.join(
        '%r: %r' % i for i in self.iteritems())

  def iteritems(self):
    found = set()
    for original in self.originals:
      for k, v in original.iteritems():
        if k not in found:
          yield k, v
          found.add(k)

  def items(self):
    return list(self.iteritems())

  def keys(self):
    return list(k for k, _ in self.iteritems())

  def values(self):
    return list(v for _, v in self.iteritems())

  • ChainMap被反向移植到早期的Python:https://pypi.python.org/pypi/chainmap (4认同)
  • 到目前为止,我已经看到一些答案引用了名为“ ChainMap”的类,该类仅在Python 3中可用,并且或多或少地执行了我的代码。对于我没有足够仔细地阅读所有内容而感到羞耻。但是鉴于这仅适用于Python 3,请以我的回答作为对Python 2用户的贡献;-) (2认同)

ShadowRanger.. 5

由于PEP 572:Assignment Expressions,Python 3.8发行版(计划于2019年10月20日)将提供一个新选项。新的赋值表达式运算符使您可以分配的结果,并仍然使用它来调用,从而使组合的代码成为单个表达式,而不是两个语句,从而进行了更改::=copyupdate

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

至:

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

同时在各个方面都表现相同。如果还必须返回结果dict(您要求返回的表达式dict;上面创建并分配给newdict,但没有返回,因此您不能使用它将参数直接传递给函数la myfunc((newdict := dict1.copy()).update(dict2))) ,然后将其添加or newdict到末尾(因为updatereturns None是虚假的,因此它将求值并newdict作为表达式的结果返回):

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

重要警告:通常,我不建议采用以下方法:

newdict = {**dict1, **dict2}

拆包方法更清晰(对于一开始就知道要进行广义拆包的人来说,应该这样),根本不需要名称(因此,构造一个立即传递给a的临时文件时,它会更加简洁。函数或包含在list/ tuple文字等中),并且几乎肯定也更快,在CPython上大致等同于:

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

但使用具体的dictAPI 在C层完成,因此不涉及动态方法查找/绑定或函数调用分派开销(在此(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)

其中的任一个是明显更恶心,并且包括进一步的低效(或者是临时浪费tupleNoneS表示逗号分离,或每个的无意义感实性测试updateNone用于返回or分离)。

赋值表达式方法的唯一真正优势在于:

  1. 您有需要同时处理sets和dicts的通用代码(它们都支持copyupdate,因此代码大致可以按您期望的那样工作)
  2. 您希望接收任意类似dict的对象,而不仅仅是对象dict本身,并且必须保留左侧的类型和语义(而不是以简单的结尾dict)。尽管myspecialdict({**speciala, **specialb})可能会起作用,但它会涉及一个额外的临时操作dict,并且如果myspecialdict具有平原dict无法保留的功能(例如,常规dicts现在基于键的首次出现保留顺序,而基于键的最后出现保留值;您可能想要一个根据最后一个保留订单键的外观,因此更新值也会将其移到末尾),那么语义将是错误的。由于赋值表达式版本使用命名方法(可能会重载以使其正常运行),因此它根本不会创建一个dict(除非dict1已经是一个dict),并保留原始类型(和原始类型的语义),同时避免任何临时性。


归档时间:

查看次数:

1682956 次

最近记录:

10 月 前