python dict update - 如何在單個表達式中合併兩個詞典?





15 Answers

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

z = dict(x, **y)

這將根據您的需要將最終的dict放在z ,並使鍵b的值被第二個( y )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

如果你使用Python 3,它只是稍微複雜一點。 要創建z

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

我有兩個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獲得最終合併的dict,而不是x

(要清楚的是, dict.update()的最後一次勝利衝突處理也是我正在尋找的。)




另一個更簡潔的選擇:

% 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

注意 :這已經成為一個流行的答案,但重要的是要指出,如果y有任何非字符串鍵,這一點的工作原理是濫用CPython實現細節,它在Python 3中不起作用,或者在PyPy,IronPython或Jython中。 此外, Guido不是粉絲 。 所以我不推薦這種技術用於前向兼容或交叉實現的可移植代碼,這實際上意味著它應該完全避免。




在後續回答中,您詢問了這兩種備選方案的相對錶現:

% 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

在我的機器上,至少(相當普通的x86_64運行Python 2.5.2),替代z2不僅更短更簡單,而且速度更快。 您可以使用Python附帶的timeit模塊timeit驗證。

示例1:將20個連續整數映射到自身的相同字典:

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

z2獲勝3.5倍左右。 不同的詞典似乎產生了截然不同的結果,但z2似乎總是領先。 (如果同一測試的結果不一致,請嘗試使用大於默認值3的數字傳入-r 。)

示例2:非重疊字典將252個短字符串映射為整數,反之亦然:

% 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

z2贏了大約10倍。這在我的書中是一個相當大的勝利!

在比較了這兩個之後,我想知道z1的糟糕表現是否可歸因於構建兩個項目列表的開銷,這反過來又讓我想知道這種變化是否會更好:

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

一些快速測試,例如

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

讓我得出結論, z3z1快一些,但不如z2快。 絕對不值得所有額外打字。

這個討論仍然缺少重要的東西,這是對這些備選方案的性能比較與合併兩個列表的“明顯”方式:使用update方法。 為了嘗試使表達式保持平等,沒有一個修改x或y,我將復制x而不是就地修改它,如下所示:

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

一個典型的結果:

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

換句話說, z0z2似乎具有基本相同的性能。 你認為這可能是巧合嗎? 我不....

事實上,我甚至聲稱純Python代碼不可能比這更好。 如果你能在C擴展模塊中做得更好,我想Python人員可能會有興趣將你的代碼(或你的方法的變體)合併到Python核心中。 Python在許多地方使用dict ; 優化其運營是一件大事。

你也可以這樣寫

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

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

print deepupdate(pluto_original, pluto_update)

正如Tony所做的那樣,但(並不奇怪)記譜法的差異原來並沒有對性能產生任何可衡量的影響。 使用適合您的任何一種。 當然,他絕對正確地指出雙語句版本更容易理解。




在Python 3中,您可以使用collections.ChainMap將多個dicts或其他映射組合在一起以創建單個可更新視圖:

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



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

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

它比dict(x.items() + y.items())快,但不如n = copy(a); n.update(b)n = copy(a); n.update(b) n = copy(a); n.update(b) ,至少在CPython上。 如果你將iteritems()更改為items() ,這個版本也適用於Python 3,這是由2to3工具自動完成的。

就個人而言,我最喜歡這個版本,因為它在單一功能語法中描述了我想要的東西。 唯一的小問題是,從y的值優先於x的值,並沒有完全明顯,但我不認為很難弄明白。




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

對於在兩個詞典('b')中都帶有鍵的項目,您可以通過將最後一個鍵入到輸出中來控制哪一個最終出現在輸出中。




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

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

在這些陰暗和可疑的答案中,這個光輝的例子是合併Python中的dicts的唯一好方法,由生活的獨裁者Guido van Rossum自己贊同! 其他人建議這一半,但沒有把它放在一個功能。

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

得到:

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



是pythonic。 使用comprehension

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



濫用導致馬修回答的單表達式解決方案:

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

你說你想要一個表達式,所以我濫用lambda來綁定一個名字,並使用元組來覆蓋lambda的一個表達式限制。 隨意畏縮。

如果你不關心複製它,你當然也可以這樣做:

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






儘管這個淺層詞典的答案很好,但這裡定義的方法實際上並沒有進行深層詞典合併。

示例如下:

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

人們會期待這樣的結果:

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

相反,我們得到這個:

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

如果它真的是一個合併,那麼'one'條目應該有'depth_2'和'extra'作為其字典中的項目。

使用鏈也不起作用:

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

結果是:

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

rcwesick給出的深度合併也會產生相同的結果。

是的,它可以合併樣本字典,但它們都不是合併的通用機制。一旦我編寫了一個執行真正合併的方法,我將在稍後更新。




借鑒這里和其他地方的想法,我理解了一個功能:

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}

用法(在python 3中測試):

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

你可以使用lambda代替。




我在今天列出的解決方案中遇到的問題是,在合併的字典中,鍵“b”的值是10但是,按照我的想法,它應該是12.在這種情況下,我提出以下內容:

from functools import reduce

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

結果:

dict1 = {'a':1}
dict2 = {'b':2}
new_dict = {**dict1, **dict2}
>>>new_dict
{'a':1, 'a':2}



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

這應該可以解決您的問題。




在Python 3.5中,您可以使用unpack **來創建新的字典。這種方法在過去的答案中沒有顯示出來。此外,最好使用{}而不是dict()。因為{}是python文字而且dict()涉及函數調用。

 dict1 = {'a':1} dict2 = {'b':2} new_dict = {**dict1, **dict2} >>>new_dict {'a':1, 'a':2} 



Related