2 つの Python 辞書を 1 つの式にマージするにはどうすればよいですか?
辞書x
およびy
の場合、は、 からの値を置換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 番目のディクショナリの値が最初のディクショナリの値を上書きすることです。
>>> z
{'a': 1, 'b': 3, 'c': 4}
このための新しい構文はPEP 448で提案され、 Python 3.5で利用可能になりました。
z = {**x, **y}
そして、それは確かに単一の表現です。
リテラル表記でもマージできることに注意してください。
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
どちらのアプローチでも、が 2 番目に来て、その値がの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
そして、あなたは単一の式を持っています:
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 で機能します。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 では、dict_items
2 つのリストではなく 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()
Python 2.7) での結合の取得も、値がハッシュ不可能なオブジェクト (リストなど) の場合に失敗します。値がハッシュ可能であっても、セットは意味的に順序付けされていないため、優先順位に関して動作は定義されていません。だからこれをしないでください:
>>> 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
代わりに from の値が保持されます。x
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}
使用してはいけない別のハック:
z = dict(x, **y)
これはdict
コンストラクターを使用し、非常に高速でメモリ効率が高くなります (2 段階のプロセスよりもわずかに優れています)。 )、読みにくく、意図した使用法ではないため、Pythonic ではありません。
django で修正されている使用例を次に示します。
frozenset
辞書はハッシュ可能なキー ( s やタプルなど)を取ることを目的としていますが、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) は、「x.update(y) を呼び出して x を返す」ための「クールなハック」として出回っているようです。個人的にはカッコいいというより卑怯です。
私の理解 (および言語の作成者の理解) の使用dict(**y)
目的は、読みやすさを目的として辞書を作成することです。
dict(a=1, b=10, c=11)
それ以外の
{'a': 1, 'b': 10, 'c': 11}
Guidoが言っていることにもかかわらずdict(x, **y)
、辞書の仕様に沿っています。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}
ネストされた辞書を処理していないようです。ネストされたキーの内容は、マージされずに上書きされるだけです [...] 再帰的にマージされないこれらの回答に私は燃え尽きてしまい、誰も言及しなかったことに驚きました。「マージ」という言葉の私の解釈では、これらの回答は「ある辞書を別の辞書で更新する」ことを説明しており、マージはしていません。
はい。1 つの式で、最初の値が 2 番目の値によって上書きされる、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: {}}}
他の値タイプの不測の事態を考え出すことは、この質問の範囲をはるかに超えているため、「辞書の辞書のマージ」に関する標準的な質問に対する私の答えを指摘します。
これらのアプローチはパフォーマンスが低下しますが、正しい動作を提供します。より高い抽象化レベルで各キーと値のペアを反復処理するため、andまたは新しいアンパックよりもパフォーマンスが大幅に低下しますが、優先順位は尊重されます (後者の辞書が優先されます)。copy
update
dict 内包表記内で手動で辞書をチェーンすることもできます:
{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
辞書に関するリソース