Pythonの辞書を結合する方法は複数ありますが、Python 3.9以降では読みやすく安全に結合できる演算子が導入されました。
本記事では、3つの実用的なマージ手法を丁寧に比較しながら、重複キーの扱いや破壊的変更の有無、互換性やパフォーマンスの注意点を段階を踏んで解説します。
最後に早見表も用意しますので、ケースに応じた最適な選択ができるようになります。
方法1: マージ演算子(|)で新しい辞書を作る
Python 3.9以上で使える辞書のマージ演算子|
は、新しい辞書を返すため、元の辞書を壊さずに結合できます。
読みやすさにも優れ、初心者にもおすすめです。
基本の書き方と結果
まずは最小の例から確認します。
a | b
は、左の辞書a
と右の辞書b
を結合した新しい辞書を返します。
# Python 3.9+
# 基本のマージ: 新しい辞書が返る(元のa, bは変わらない)
a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}
c = a | b # 新しい辞書を作成
print("a:", a) # 元のaはそのまま
print("b:", b) # 元のbもそのまま
print("c:", c) # マージ結果
print("a is c:", a is c) # 別オブジェクトかを確認
a: {'x': 1, 'y': 2}
b: {'y': 99, 'z': 3}
c: {'x': 1, 'y': 99, 'z': 3}
a is c: False
元の辞書を保持したい場合は|
を使うのが最も簡単です。
キー重複時の優先順位(bが勝つ)
重複キーは右側(=後勝ち)です。
つまりa | b
ではbが勝ちます。
# 右辺(b)が勝つ例
a = {"k": 1, "list": [1, 2], "inner": {"a": 1}}
b = {"k": 2, "list": ["x"], "inner": {"b": 2}}
c = a | b
print(c)
{'k': 2, 'list': ['x'], 'inner': {'b': 2}}
ここで特に重要なのは「浅いマージ」である点です。
"inner"
のようなネストした辞書は丸ごと置き換わるため、深い階層のキーを個別にマージしてくれるわけではありません。
方法2: 更新代入(|=)で既存の辞書を更新
|=
は更新代入(破壊的更新)です。
左辺の辞書そのものを書き換えたいときに使います。
破壊的に結合する場合
オブジェクト自体を更新するため、メモリコピーを避けたい場合にも適しています。
# Python 3.9+
# 破壊的なマージ: a自体が更新される
a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}
before_id = id(a)
a |= b # aを更新(破壊的)
print("a:", a)
print("id(a)は変わらないか:", before_id == id(a))
a: {'x': 1, 'y': 99, 'z': 3}
id(a)は変わらないか: True
代入先の辞書をそのまま使い続けたいときは|=
が便利です。
dict.updateとの差異
dict.update
も破壊的更新ですが、受け付ける引数の種類に差があります。
|=
: 原則として「マッピング(辞書型)」が必要です。dict.update
: マッピングに加えて、(key, value)のイテラブルやキーワード引数も受け取れます。
# 引数の受け付け方の違い
# |= は「マッピング(辞書)」が必要
x = {"a": 1}
x |= {"b": 2} # OK
try:
x |= [("c", 3)] # リストのタプル(イテラブル)はNG → TypeError
except TypeError as e:
print("TypeError:", e)
# update はイテラブルやキーワード引数もOK
y = {"a": 1}
y.update([("c", 3)]) # OK
y.update(d=4) # キーワード引数もOK
print("y:", y)
TypeError: unsupported operand type(s) for |: 'dict' and 'list'
y: {'a': 1, 'c': 3, 'd': 4}
柔軟に受け取りたいならupdate
、辞書同士の更新で簡潔に書きたいなら|=
という使い分けが実用的です。
方法3: アンパック({**a, **b})で互換的に結合
{**a, **b}
はPython 3.5以降で使える辞書リテラルのアンパックです。
新しい辞書を生成する点は|
と同じですが、古いPythonへの互換性が必要なときに選びます。
Python 3.5+で使える書き方
# Python 3.5+
# アンパックで新しい辞書を作成
a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}
c = {**a, **b} # 後から展開されたbが勝つ
print(c)
{'x': 1, 'y': 99, 'z': 3}
パフォーマンスと注意点(浅いマージ)
- 浅いマージ: ネストした辞書は置き換えになります。深い階層をマージしたい場合は個別に対応する必要があります。
- パフォーマンス:
|
や|=
はCPythonで最適化されています。{**a, **b}
は新しい辞書を構築する都合上、やや不利になることが多いですが、データ量が小さい場合は体感差が出ないこともあります。
浅いマージの具体例を示します。
# 浅いマージの挙動(ネストは置き換え)
cfg1 = {"db": {"host": "localhost", "port": 5432}, "debug": True}
cfg2 = {"db": {"port": 6543}} # portだけ変えたいケース
merged_pipe = cfg1 | cfg2 # 3.9+
merged_unpack = {**cfg1, **cfg2} # 3.5+
print("merged_pipe:", merged_pipe)
print("merged_unpack:", merged_unpack)
merged_pipe: {'db': {'port': 6543}, 'debug': True}
merged_unpack: {'db': {'port': 6543}, 'debug': True}
“host”が消えていることに注意してください。
深いマージが必要なら、各キーごとに別途マージします。
# 簡易的な「部分的に深い」マージ例(必要なキーだけネストを個別にマージ)
cfg1 = {"db": {"host": "localhost", "port": 5432}, "debug": True}
cfg2 = {"db": {"port": 6543}}
merged = {**cfg1, **cfg2}
# "db" は両方にあるので、ネストを個別にマージする
if "db" in cfg1 and "db" in cfg2:
merged["db"] = {**cfg1["db"], **cfg2["db"]}
print(merged)
{'db': {'host': 'localhost', 'port': 6543}, 'debug': True}
参考までに、簡単なタイミング比較の例です(環境により結果は変わります)。
# 簡易ベンチマーク(環境により結果は異なるため目安)
import timeit
setup = """
a = {str(i): i for i in range(1000)}
b = {str(i): -i for i in range(500, 1500)}
"""
print("a | b:", timeit.timeit("a | b", setup=setup, number=1000))
print("{**a, **b}:", timeit.timeit("{**a, **b}", setup=setup, number=1000))
print("c=a.copy(); c.update(b):", timeit.timeit("c=a.copy(); c.update(b)", setup=setup, number=1000))
print("a_copy=a.copy(); a_copy|=b:", timeit.timeit("a_copy=a.copy(); a_copy|=b", setup=setup, number=1000))
a | b: 例) 0.02~0.05秒
{**a, **b}: 例) 0.03~0.08秒
c=a.copy(); c.update(b): 例) 0.02~0.05秒
a_copy=a.copy(); a_copy|=b: 例) 0.02~0.05秒
大規模データでは破壊的更新|=
やupdate
が有利になることが多いですが、元の辞書を壊してよいかで最優先に選び分けるのが実務的です。
マージ方法早見表
以下は3つの主要な方法(+参考としてdict.update
)の比較です。
方法 | 記法 | Pythonバージョン | 既存を書き換えるか | 重複キーの挙動 | 右オペランドの受け付け | 主な用途/特徴 | |
---|---|---|---|---|---|---|---|
マージ演算子 | a | b | 3.9+ | 書き換えない(新しい辞書) | 右(後)が勝つ | マッピング(辞書) | 安全で読みやすい結合 |
更新代入 | a | = b | 3.9+ | 書き換える(破壊的) | 右(後)が勝つ | マッピング(辞書) | 高速・メモリ効率、元を更新 |
アンパック | {**a, **b} | 3.5+ | 書き換えない(新しい辞書) | 後が勝つ | マッピング(辞書) | 互換性重視、読みやすいリテラル | |
参考:update | d.update(b) | 3.0+ | 書き換える(破壊的) | 右(後)が勝つ | マッピング/イテラブル/キーワード | 柔軟な引数、従来からの定番 |
ポイントは「新しい辞書が欲しいか」「元を更新してよいか」「右側が勝つ」の3点です。
まとめ
本記事では、辞書のマージを行う3つの方法(|
、|=
、{**a, **b}
)を丁寧に比較しました。
新しい辞書が必要なら|
または{**a, **b}
、元の辞書を更新してよいなら|=
やupdate
を選ぶのが基本です。
さらに、重複キーは常に右が勝つ点と、マージは浅い(ネストは置き換え)点を押さえておけば、日常のデータマージで迷わず安全に使い分けができます。
必要に応じてネストの一部だけを個別にマージする工夫も取り入れてみてください。