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

时间:2020-03-05 18:46:17  来源:igfitidea点击:

我有两个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()的最后一个胜利处理。)

解决方案

回答

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

对于两个字典中都有键的项目,我们可以通过将最后一个放在输出中来控制哪一个最终出现在输出中。

回答

在情况下,我们可以执行以下操作:

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

这将根据需要,将最后一个字典放在z中,并使键b的值被第二个(y)字典值正确覆盖:

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

回答

另一个更简洁的选择:

z = dict(x, **y)

注意:这已经成为一个流行的答案,但是必须指出的是,如果y具有任何非字符串键,那么它实际上是有效的,这是对CPython实现细节的滥用,并且不适用于CPython实现细节。 Python 3,或者PyPy,IronPython或者Jython。另外,Guido也不是粉丝。因此,我不建议将此技术用于前向兼容或者交叉实现的可移植代码,这实际上意味着应完全避免使用它。

回答

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

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

回答

这可能不是一个流行的答案,但是我们几乎可以肯定不想这样做。如果要合并的副本,请使用副本(或者深度复制,取决于所需内容),然后进行更新。与使用.items()+ .items()进行单行创建相比,两行代码的Python语言更具可读性。显式胜于隐式。

此外,当我们使用.items()(Python 3.0之前的版本)时,我们正在创建一个新列表,其中包含字典中的项目。如果字典很大,那将是很多开销(创建合并字典后将立即丢弃两个大列表)。 update()可以更高效地工作,因为它可以逐项执行第二个字典。

在时间方面:

>>> 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()将在较旧的版本中工作。

回答

在后续回答中,我们询问了这两种选择的相对性能:

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总是总是遥遥领先。 (如果我们在同一测试中得到的结果不一致,请尝试传递-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一样,但是(并不奇怪)表示法上的差异对性能没有任何可测量的影响。使用我们认为合适的任何一种。当然,他指出两语句版本更容易理解是绝对正确的。

回答

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

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的值并不能完全清楚,但是我不认为很难弄清楚。