Pythonで2つの辞書を1つの式にマージするにはどうすればよいですか(辞書の和集合を取る)?

2008年09月02日に質問されました。  ·  閲覧回数 2M回  ·  ソース

Carl Meyer picture
2008年09月02日

私は2つのPython辞書を持っており、これら2つの辞書をマージして返す(つまり、和集合をとる)単一の式を記述したいと思います。 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 zでその最終的なマージされた辞書を取得するにはどうすればよいですか?

(さらに明確にするために、 dict.update()の最後の1勝の競合処理も私が探しているものです。)

回答

Aaron Hall picture
2014年11月11日
6422

2つのPython辞書を1つの式にマージするにはどうすればよいですか?

辞書xおよびy場合、 zは、 x値をy置き換えて、浅くマージされた辞書になります。

  • 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)
    
  • Python 3.9.0以降(2020年10月17日リリース):これをさらに簡素化するために、ここPEP-584が実装されました。
    z = x | y          # NOTE: 3.9+ ONLY
    

説明

2つの辞書があり、元の辞書を変更せずにそれらを新しい辞書にマージするとします。

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

望ましい結果は、値がマージされた新しいディクショナリ( z )を取得し、2番目のディクショナリの値が最初のディクショナリの値を上書きすることです。

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

このための新しい構文は、 PEP 448で提案され、 利用可能です。

z = {**x, **y}

そしてそれは確かに単一の表現です。

リテラル表記とマージすることもできることに注意してください。

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

そして今:

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

現在、3.5、Python3.5ドキュメントの新機能に組み込まれています。

ただし、多くの組織はまだPython 2を使用しているため、下位互換性のある方法でこれを実行することをお勧めします。 Python2とPython3.0-3.4で利用できる古典的なPythonの方法は、これを2段階のプロセスとして実行することです。

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

どちらのアプローチでも、 yが2番目に来て、その値がxの値に置き換わるため、 'b'は最終結果で3を指します。

Python 3.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で機能します。 例:与えられた辞書aからg

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

gキーと値のペアは、辞書aからfなどよりも優先されます。

他の回答の批評

以前に受け入れられた回答に表示されているものを使用しないでください。

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

Python 2では、dictごとにメモリ内に2つのリストを作成し、最初の2つを合わせた長さに等しい長さの3番目のリストをメモリ内に作成し、3つすべてのリストを破棄してdictを作成します。 Python 3では、 2つのリストではなく2つの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())) 。 これはリソースと計算能力の浪費です。

同様に、Python 3でitems() (Python 2.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コンストラクターを使用し、非常に高速でメモリ効率が高く(2ステップのプロセスよりもわずかに多い)、ここで何が起こっているかを正確に理解していない限り(つまり、2番目のdictが渡されます) dictコンストラクターへのキーワード引数として)、読むのが難しく、意図された使用法ではないため、Pythonicではありません。

これは、 django

辞書はハッシュ可能なキー(フリーズセットやタプルなど)を取得することを目的としていますが、キーが文字列でない場合、このメソッドはPython3で

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

メーリングリストから、言語の作成者であるGuido vanRossumは次のように書いています。

結局のところ、それは**メカニズムの乱用であるため、dict({}、** {1:3})を違法と宣言しても問題ありません。

そして

どうやらdict(x、** y)は、「x.update(y)を呼び出してxを返す」の「クールハック」として回っています。 個人的には、かっこいいというより卑劣だと思います。

dict(**y)使用目的は、読みやすさを目的とした辞書の作成であるというのが私の理解(および言語

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

の代わりに

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

コメントへの回答

Guidoの言うことにもかかわらず、 dict(x, **y)はdictの仕様と一致しています。 Python 2と3の両方で機能します。これが文字列キーでのみ機能するという事実は、キーワードパラメータがどのように機能するかという直接的な結果であり、dictの短期間の結果ではありません。 また、この場所で**演算子を使用してメカニズムを悪用することもありません。実際、**は辞書をキーワードとして渡すように正確に設計されています。

繰り返しますが、キーが文字列以外の場合、3では機能しません。 暗黙の呼び出しコントラクトでは、名前空間は通常の辞書を使用しますが、ユーザーは文字列であるキーワード引数のみを渡す必要があります。 他のすべての呼び出し可能オブジェクトはそれを強制しました。 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で修正されました。

言語の1つのバージョンでのみ機能するコード、または特定の任意の制約が与えられた場合にのみ機能するコードを意図的に作成することは、悪意のある無能であることをお伝えします。

その他のコメント:

dict(x.items() + y.items())は、Python2にとって依然として最も読みやすいソリューションです。読みやすさが重要です。

私の回答: merge_two_dicts(x, y)は、実際に読みやすさを懸念している場合、実際にははるかに明確に思えます。 また、Python 2はますます非推奨になっているため、上位互換性はありません。

{**x, **y}はネストされた辞書を処理していないようです。 ネストされたキーの内容は単純に上書きされ、マージされません[...]再帰的にマージされないこれらの回答に焼けてしまい、誰も言及しなかったことに驚きました。 「マージ」という言葉の私の解釈では、これらの回答は「ある辞書を別の辞書に更新する」ことを表しており、マージではありません。

はい。 1つの式で、最初の値が2番目の値で上書きされる浅いマージを要求している質問に戻って参照する必要があります。

ディクショナリのディクショナリが2つあるとすると、1つはそれらを単一の関数に再帰的にマージしますが、どちらのソースからのディクショナリも変更しないように注意する必要があります。これを回避する最も確実な方法は、値を割り当てるときにコピーを作成することです。 キーはハッシュ可能である必要があり、通常は不変であるため、キーをコピーすることは無意味です。

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

他の値型の偶発性を考え出すことはこの質問の範囲をはるかに超えているので、「辞書の辞書のマージ」に関する標準的な質問に対する私の答えを示します。

パフォーマンスは劣るが、アドホックは正しい

これらのアプローチはパフォーマンスが低下しますが、正しい動作を提供します。 これらは、より高いレベルの抽象化で各Key-Valueペアを反復処理するため、 copyおよびupdateまたは新しいアンパックよりもパフォーマンスが大幅に低下しますが、優先順位尊重され

dict内包表記内で辞書を手動でチェーンすることもできます。

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

Python 3.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

辞書に関するリソース

Thomas Vander Stichele picture
2008年09月02日
1659

あなたの場合、あなたができることは次のとおりです。

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

これにより、必要に応じて、最終的なdictがz 、キーbの値が2番目の( 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}

Python 2を使用している場合は、 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}
Matthew Schinckel picture
2008年09月02日
660

別の方法:

z = x.copy()
z.update(y)
Carl Meyer picture
2008年09月02日
369

別の、より簡潔なオプション:

z = dict(x, **y)

:これは一般的な回答になっていますが、 yに文字列以外のキーがある場合、これがまったく機能するという事実はCPython実装の詳細の乱用であり、実際に機能することを指摘することが重要です。 Python 3、またはPyPy、IronPython、またはJythonでは機能しません。 また、 Guidoはファンではありません。 したがって、この手法を上位互換性または相互実装のポータブルコードに推奨することはできません。つまり、完全に回避する必要があります。

Tony Meyer picture
2008年09月08日
230

これはおそらく一般的な答えではないでしょうが、あなたはほぼ間違いなくこれをしたくないでしょう。 マージであるコピーが必要な場合は、コピー(または必要に応じて

さらに、.items()(Python 3.0より前)を使用すると、dictのアイテムを含む新しいリストが作成されます。 辞書が大きい場合、それはかなりのオーバーヘッドになります(マージされた辞書が作成されるとすぐに破棄される2つの大きなリスト)。 update()は、2番目の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は、最初の2つの間のわずかな速度低下は、読みやすさのために価値があります。 さらに、辞書作成用のキーワード引数はPython 2.3でのみ追加されましたが、copy()とupdate()は古いバージョンで機能します。

zaphod picture
2008年10月23日
164

フォローアップの回答で、次の2つの選択肢の相対的なパフォーマンスについて質問しました。

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常に前に出てくるようです。 (同じテストで一貫性のない結果が得られる場合は、デフォルトの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倍勝ちます。それは私の本ではかなり大きな勝利です!

これら2つを比較した後、 z1のパフォーマンスの低下は、2つのアイテムリストを作成するオーバーヘッドに起因するのではないかと考えました。その結果、このバリエーションの方がうまくいくのではないかと思いました。

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 。 余分な入力をすべて行う価値はありません。

この議論にはまだ重要なことが欠けています。それは、これらの選択肢と、2つのリストをマージする「明白な」方法( 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と同じですが、(当然のことながら)表記法の違いはパフォーマンスに測定可能な影響を与えないことがわかりました。 自分に合ったものを使用してください。 もちろん、彼は2ステートメントバージョンの方がはるかに理解しやすいと指摘するのは絶対に正しいです。

Raymond Hettinger picture
2013年04月28日
141

Python 3.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

Python 3.5以降の更新PEP448拡張辞書のパッキングとアンパックを使用できます。 これは速くて簡単です:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
rcreswick picture
2008年09月05日
127

似たようなものが欲しかったのですが、重複キーの値をどのようにマージするかを指定できるので、これをハックしました(ただし、あまりテストしませんでした)。 明らかに、これは単一の式ではありませんが、単一の関数呼び出しです。

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
Stan picture
2011年11月29日
101

再帰的/ディープアップデートディクテーション

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 picture
2010年10月15日
80

コピーを使用していないときに私が考えることができる最高のバージョンは次のとおりです。

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()items()に変更した場合にも、Python 3で機能します。これは、2to3ツールによって自動的に行われます。

個人的には、このバージョンが最も気に入っています。単一の機能構文で必要なものがかなり適切に記述されているからです。 唯一の小さな問題は、yの値がxの値よりも優先されることを完全に明らかにしていないことですが、それを理解するのは難しいとは思いません。