update - python two dict add




如何在單個表達式中合併兩個詞典? (20)

我有兩個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詞典?

對於字典xyz成為淺合併字典, y值替換x

  • 在Python 3.5或更高版本中:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • 在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.copy()
z.update(y) # which returns None since it mutates z

現在:

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

它現在顯示為在3.5,PEP 478發佈時間表中實現,現在它已經進入了Python 3.5文檔中的新功能

但是,由於許多組織仍在使用Python 2,因此您可能希望以向後兼容的方式執行此操作。 Python 2和Python 3.0-3.4中提供的經典Pythonic方法是通過兩個步驟完成的:

z = merge_two_dicts(x, y)

在這兩種方法中, y將成為第二個,其值將替換x的值,因此在最終結果中'b'將指向3

還沒有在Python 3.5上,但想要一個表達式

如果你還沒有使用Python 3.5,或者需要編寫向後兼容的代碼,並且你想在單個表達式中使用它 ,那麼最正確的方法就是將它放在一個函數中:

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

然後你有一個表達式:

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

您還可以創建一個函數來合併未定義數量的dicts,從零到非常大的數字:

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

對於所有dicts,此函數將在Python 2和3中使用。 例如,給出a字母ag

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

g鍵值對優先於dicts af ,依此類推。

批評其他答案

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

>>> c = dict(a.items() | b.items())

在Python 2中,您在內存中為每個dict創建兩個列表,在內存中創建第三個列表,其長度等於放在一起的前兩個列表的長度,然後丟棄所有三個列表以創建dict。 在Python 3中,這將失敗,因為您將兩個dict_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'

你必須明確地將它們創建為列表,例如z = dict(list(x.items()) + list(y.items())) 。 這是浪費資源和計算能力。

類似地,當值是不可用的對象(例如列表)時,將Python 3中的items()聯合items() Python 2.7中的viewitems() )也會失敗。 即使您的值是可清除的, 因為集合在語義上是無序的,所以行為在優先級方面是未定義的。 所以不要這樣做:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

此示例演示了值不可用時會發生什麼:

z = dict(x, **y)

這是y應該具有優先權的示例,但是由於任意順序的集合而保留x中的值:

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

另一個黑客你不應該使用:

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

這使用了dict構造函數,並且非常快且內存效率高(甚至比我們的兩步過程稍微多一些)但除非你確切知道這裡發生了什麼(也就是說,第二個dict作為關鍵字參數傳遞給dict構造函數),它很難讀,它不是預期的用法,所以它不是Pythonic。

這是django修復的用法示例。

Dicts旨在獲取可以使用的密鑰(例如frozensets或tuples),但是當密鑰不是字符串時此方法在Python 3中失敗。

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

郵件列表中 ,該語言的創建者Guido van Rossum寫道:

我很好地宣布dict({},** {1:3})是非法的,因為它畢竟是濫用**機制。

顯然dict(x,** y)作為“調用x.update(y)並返回x”的“酷黑客”。 就個人而言,我發現它比酷酷更卑鄙。

我的理解(以及對語言創建者的理解) dict(**y)的預期用途是為了可讀性目的而創建dicts,例如:

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

代替

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

對評論的回應

儘管Guido說, dict(x, **y)符合dict規範,順便說一下。 適用於Python 2和3.事實上,這僅適用於字符串鍵,這是關鍵字參數如何工作而不是dict短路的直接結果。 在這個地方也沒有使用**運算符濫用該機制,事實上**的設計恰恰是為了將dicts作為關鍵字傳遞。

同樣,當鍵是非字符串時,它不適用於3。 隱式調用契約是命名空間採用普通的dicts,而用戶只能傳遞字符串的關鍵字參數。 所有其他callables強制執行它。 dict打破了Python 2中的這種一致性:

dict((k, v) for d in dicts for k, v in d.items())

鑑於Python的其他實現(Pypy,Jython,IronPython),這種不一致性很糟糕。 因此它在Python 3中得到了修復,因為這種用法可能是一個突破性的變化。

我向你提出,故意編寫只能在一種語言版本中工作的代碼或僅在某些任意約束條件下工作的代碼是惡意無能的。

更多評論:

dict(x.items() + y.items())仍然是Python 2最易讀的解決方案。可讀性很重要。

我的回答: merge_two_dicts(x, y)實際上對我來說更清楚,如果我們真的關心可讀性的話。 並且它不向前兼容,因為Python 2越來越被棄用。

{**x, **y}似乎不處理嵌套字典。 嵌套鍵的內容被簡單地覆蓋,沒有合併[...]我最終被這些沒有遞歸合併的答案所燒毀,我很驚訝沒有人提到它。 在我對“合併”一詞的解釋中,這些答案描述了“用另一個更新一個字典”,而不是合併。

是。 我必須回過頭來回答一個問題,即要求兩個詞典的淺層合併,第一個的值被第二個詞覆蓋 - 在一個表達式中。

假設有兩個詞典字典,一個可以遞歸地將它們合併到一個函數中,但是你應該注意不要從任何一個源修改dicts,並且最可靠的方法是在分配值時進行複制。 由於密鑰必須是可清洗的,因此通常是不可變的,因此復制它們是沒有意義的:

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

用法:

import timeit

提出其他價值類型的突發事件遠遠超出了這個問題的範圍,所以我將指出你對“詞典合併詞典”的規範問題的回答 。

性能較差但正確的Ad-hoc

這些方法性能較差,但它們會提供正確的行為。 它們的copyupdate或新解包的性能要低得多,因為它們在更高的抽象級別迭代每個鍵值對,但它們確實遵循優先順序(後面的dicts優先)

你也可以在dict理解中手動鏈接dicts:

>>> 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 2.6中(當引入生成器表達式時可能早在2.4):

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

itertools.chain將以正確的順序將迭代器鏈接到鍵值對:

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

績效分析

我只會對已知行為正確的用法進行性能分析。

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

以下是在Ubuntu 14.04上完成的

在Python 2.7(系統Python)中:

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

在Python 3.5(deadsnakes PPA)中:

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

字典資源


遞歸/深度更新字典

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}

輸出:

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

謝謝rednaw的編輯。



使用保留順序的itertools的簡單解決方案(後面的dicts優先)

import collections
a = {1: 1, 2: 2}
b = {2: 3, 3: 4}
c = {3: 5}

r = dict(collections.ChainMap(a, b, c))
print(r)

它的用法是:

{1: 1, 2: 2, 3: 4}

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

示例如下:

{'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給出的深度合併也會產生相同的結果。

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


另一個更簡潔的選擇:

% 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不是粉絲 。 所以我不推薦這種技術用於前向兼容或交叉實現的可移植代碼,這實際上意味著它應該完全避免。



在python3中, items方法不再返回列表 ,而是返回一個視圖 ,它就像一個集合。 在這種情況下,你需要採用set union,因為與+連接將不起作用:

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

對於2.7版中類似python3的行為, viewitems方法應該代替items

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

不管怎樣我更喜歡這種符號,因為將它看作是一個聯合操作而不是連接似乎更自然(如標題所示)。

編輯:

python 3還有幾點。首先,請注意dict(x, **y)技巧在python 3中不起作用,除非y中的鍵是字符串。

此外,Raymond Hettinger的Chainmap answer非常優雅,因為它可以使用任意數量的dicts作為參數,但是從文檔看起來它依次查看每個查找的所有dicts的列表:

查找會連續搜索基礎映射,直到找到密鑰。

如果您的應用程序中有大量查找,這會降低您的速度:

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

所以查找速度要慢一個數量級。 我是Chainmap的粉絲,但在可能有很多查找的地方看起來不那麼實用。


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

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)

如果你認為lambdas是邪惡的,那就不要再讀了。 根據要求,您可以使用一個表達式編寫快速且內存有效的解決方案:

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

如上所述,使用兩行或編寫函數可能是更好的方法。


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

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的值,並沒有完全明顯,但我不認為很難弄明白。


我在今天列出的解決方案中遇到的問題是,在合併的字典中,鍵“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}

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

替代:

% 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

這可以通過單個字典理解來完成:

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

在我看來,“單一表達”部分的最佳答案是不需要額外的功能,而且很短。


這可能不是一個流行的答案,但你幾乎肯定不想這樣做。 如果你想要一個合併的副本,那麼使用copy(或deepcopy ,取決於你想要的),然後更新。 這兩行代碼比使用.items()+ .items()的單行創建更具可讀性 - 更多Pythonic。 顯式優於隱式。

此外,當您使用.items()(Python 3.0之前)時,您正在創建一個包含dict項目的新列表。 如果你的詞典很大,那麼開銷很大(兩個大型列表一旦創建合併的dict就會被丟棄)。 update()可以更有效地工作,因為它可以逐項運行第二個dict。

time

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

IMO前兩者之間的微小減速對於可讀性是值得的。 此外,字典創建的關鍵字參數僅在Python 2.3中添加,而copy()和update()將在舊版本中使用。


(僅適用於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成功返回。)


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

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

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




merge