[解決済み】2つの辞書を1つの式でマージする(辞書の和をとる)には?)
質問
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辞書をマージすることができますか?
辞書の場合
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番目の辞書の値を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において、すべての辞書に対して動作します。
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つの
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;辞書の辞書のマージ"です。 .
性能は低いが正しいアドホックな方法
これらのアプローチは性能は低いですが、正しい動作を提供します。
これらは
より少ない
よりも性能は落ちます。
copy
と
update
や新しいアンパッキングは、より高い抽象度で各キー・バリュー・ペアを繰り返し処理するためですが
を行います。
は優先順位を尊重します (後者の辞書が優先されます)。
の中で手動で辞書を連結することもできます。 ディクショナリ内包 :
{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
辞書に関する資料
- Pythonの 辞書の実装 を 3.6 用に更新しました。
- 辞書に新しいキーを追加する方法に関する回答
- 2つのリストを辞書にマッピングする
- 辞書に関するPythonの公式ドキュメント
- 辞書はさらに強力に - Pycon 2017でのBrandon Rhodes氏による講演。
- モダンなPython辞書、素晴らしいアイデアの合流点 - Raymond Hettinger氏によるPycon 2017での講演。
関連
-
pyCaret効率化乗算器 オープンソース ローコード Python機械学習ツール
-
Python Pillow Image.save jpg画像圧縮問題
-
[解決済み】 NameError: グローバル名 'xrange' は Python 3 で定義されていません。
-
[解決済み】django インポートエラー - core.managementという名前のモジュールがない
-
[解決済み] Pythonで2つのリストを連結する方法は?
-
[解決済み] 辞書のリストを辞書の値でソートするにはどうしたらいいですか?
-
[解決済み] 2 つの Git リポジトリをマージする方法は?
-
[解決済み] 2つのリストを辞書に変換するにはどうしたらいいですか?
-
[解決済み] Git で特定のコミットをマージする方法
-
[解決済み】JavaScriptで2つの配列を結合し、項目の重複を排除する方法
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Python関数の高度な応用を解説
-
pythonサイクルタスクスケジューリングツール スケジュール詳解
-
Pythonを使って簡単なzipファイルの解凍パスワードを手作業で解く
-
パッケージングツールPyinstallerの使用と落とし穴の回避
-
PythonでECDSAを実装する方法 知っていますか?
-
[解決済み】RuntimeWarning: 割り算で無効な値が発生しました。
-
[解決済み】"No JSON object could be decoded "よりも良いエラーメッセージを表示する。
-
[解決済み】ValueError: pickleプロトコルがサポートされていません。3、python2 pickleはpython3 pickleでダンプしたファイルを読み込むことができない?
-
[解決済み】 'numpy.float64' オブジェクトは反復可能ではない
-
[解決済み] Pythonでdictオブジェクトのユニオン[重複]。