1. ホーム
  2. パイソン

[解決済み】2つの辞書を1つの式でマージする(辞書の和をとる)には?)

2022-05-10 10:18:51

質問

Pythonの辞書が2つあり、この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 ?

(余計なお世話ですが、最後に一勝する競合処理である dict.update() も私が求めているものです)。

どのように解決するのですか?

<ブロッククオート

どのように私は単一の式で2つのPython辞書をマージすることができますか?

辞書の場合 xy , z の値を持つ浅く結合された辞書になります。 y の値を x .

  • Python 3.9.0以上(2020年10月17日リリース)において。 PEP-584 , はこちら が実装され、最もシンプルな方法を提供しています。

    z = x | y          # NOTE: 3.9+ ONLY
    
    
  • Python 3.5以降で。

    z = {**x, **y}
    
    
  • Python 2, (または3.4以下)で、関数を書きます。

    def merge_two_dicts(x, y):
        z = x.copy()   # start with keys and values of x
        z.update(y)    # modifies z with keys and values of y
        return z
    
    

    となり、現在では

    z = merge_two_dicts(x, y)
    
    

説明

2つの辞書があり、元の辞書を変更することなく新しい辞書にマージしたいとします。

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

望ましい結果は、新しい辞書( z ) を取得し、2番目の辞書の値を1番目の辞書の値で上書きすることです。

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

で提案された新しい構文で PEP 448 Python 3.5 で利用可能

z = {**x, **y}

と、確かに1つの式になっています。

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

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の方法は、2段階のプロセスとしてこれを行うことです。

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 dictionaries, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

とすると、1つの式ができあがります。

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

この関数はPython 2と3において、すべての辞書に対して動作します。 ag :

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つの dict_items オブジェクトを足しているのであって、2つのリストを足しているのではないためです。

>>> 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() は、値がハッシュ化できないオブジェクト(例えばリストなど)である場合にも失敗します。) 値がハッシュ可能であっても セットは意味的に順序付けされないので、優先順位に関して動作は未定義です。だからこれをやってはいけない。

>>> 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 で改善された例です。 .

辞書は、ハッシュ可能なキーを取ることを意図しています (例えば frozenset やタプルなど)を取りますが このメソッドは、キーが文字列でない場合、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) は "cool hack" として出回っているようで、 "call x.update(y) and return x"のための "クールハック"として出回っているようです。個人的には、クールよりも卑劣だと思います。 個人的にはクールよりも卑劣だと思います。

私の理解では、( 言語の作成者 の意図された使い方は dict(**y) は読みやすさを目的とした辞書を作成するためである、など。

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

の代わりに

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

コメントへの対応

<ブロッククオート

グイドーの言うこととは裏腹に dict(x, **y) は dict の仕様に沿ったものであり、Python 2 と 3 の両方で動作します。これが文字列のキーに対してのみ機能するという事実は、キーワードパラメータがどのように機能するかの直接的な結果であり、dictの欠点ではありません。また、この場所で**演算子を使用することは、メカニズムの乱用ではありません。実際、**はキーワードとして辞書を渡すために正確に設計されました。

繰り返しになりますが、キーが文字列ではない場合、それは3に対して機能しません。暗黙の呼び出し契約は、名前空間は通常の辞書を取り、ユーザーは文字列であるキーワード引数のみを渡さなければならないというものです。他のすべてのcallableはそれを強制しました。 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で修正されました。

私は、ある言語の1つのバージョンでのみ動作するコードや、ある任意の制約を与えられたときのみ動作するコードを意図的に書くことは、悪意ある無能であると提議します。

もっとコメントを

dict(x.items() + y.items()) は、Python 2ではまだ最も読みやすい解決策です。読みやすさは重要です。

私の回答 merge_two_dicts(x, y) は、私たちが実際に可読性を気にしているならば、私にはより明確なように思えます。そして、Python 2がどんどん非推奨になっているので、前方互換性がありません。

{**x, **y} はネストした辞書を扱わないようです。ネストしたキーの内容は単に上書きされ、マージされません[...] 私は結局、再帰的にマージしないこれらの回答に焼かれてしまい、誰もそれに言及しないことに驚きました。私の解釈では、単語の "merging" これらの回答は "update one dict with another" を記述しており、マージはしていません。

はい、私はあなたに質問を差し戻さなければなりません。 浅い のマージ 辞書を使い、1つ目の値を2つ目の値で上書きする - 1つの式で。

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

他の値のタイプに対するコンティンジェンシーを考えることは、この質問の範囲をはるかに超えているので、次のサイトを紹介します。 の正規の質問に対する私の答えは、quot;辞書の辞書のマージ"です。 .

性能は低いが正しいアドホックな方法

これらのアプローチは性能は低いですが、正しい動作を提供します。 これらは より少ない よりも性能は落ちます。 copyupdate や新しいアンパッキングは、より高い抽象度で各キー・バリュー・ペアを繰り返し処理するためですが を行います。 は優先順位を尊重します (後者の辞書が優先されます)。

の中で手動で辞書を連結することもできます。 ディクショナリ内包 :

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

辞書に関する資料