閉じる

【Python】辞書をマージする3つの方法

Pythonの辞書を結合する方法は複数ありますが、Python 3.9以降では読みやすく安全に結合できる演算子が導入されました。

本記事では、3つの実用的なマージ手法を丁寧に比較しながら、重複キーの扱い破壊的変更の有無互換性やパフォーマンスの注意点を段階を踏んで解説します。

最後に早見表も用意しますので、ケースに応じた最適な選択ができるようになります。

方法1: マージ演算子(|)で新しい辞書を作る

Python 3.9以上で使える辞書のマージ演算子|は、新しい辞書を返すため、元の辞書を壊さずに結合できます。

読みやすさにも優れ、初心者にもおすすめです。

基本の書き方と結果

まずは最小の例から確認します。

a | bは、左の辞書aと右の辞書bを結合した新しい辞書を返します。

Python
# 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が勝ちます

Python
# 右辺(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
# 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)のイテラブルやキーワード引数も受け取れます。
Python
# 引数の受け付け方の違い

# |= は「マッピング(辞書)」が必要
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
# 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}は新しい辞書を構築する都合上、やや不利になることが多いですが、データ量が小さい場合は体感差が出ないこともあります。

浅いマージの具体例を示します。

Python
# 浅いマージの挙動(ネストは置き換え)

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”が消えていることに注意してください。

深いマージが必要なら、各キーごとに別途マージします。

Python
# 簡易的な「部分的に深い」マージ例(必要なキーだけネストを個別にマージ)

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}

参考までに、簡単なタイミング比較の例です(環境により結果は変わります)。

Python
# 簡易ベンチマーク(環境により結果は異なるため目安)
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バージョン既存を書き換えるか重複キーの挙動右オペランドの受け付け主な用途/特徴
マージ演算子ab3.9+書き換えない(新しい辞書)右(後)が勝つマッピング(辞書)安全で読みやすい結合
更新代入a= b3.9+書き換える(破壊的)右(後)が勝つマッピング(辞書)高速・メモリ効率、元を更新
アンパック{**a, **b}3.5+書き換えない(新しい辞書)後が勝つマッピング(辞書)互換性重視、読みやすいリテラル
参考:updated.update(b)3.0+書き換える(破壊的)右(後)が勝つマッピング/イテラブル/キーワード柔軟な引数、従来からの定番

ポイントは「新しい辞書が欲しいか」「元を更新してよいか」「右側が勝つ」の3点です。

まとめ

本記事では、辞書のマージを行う3つの方法(||={**a, **b})を丁寧に比較しました。

新しい辞書が必要なら|または{**a, **b}元の辞書を更新してよいなら|=updateを選ぶのが基本です。

さらに、重複キーは常に右が勝つ点と、マージは浅い(ネストは置き換え)点を押さえておけば、日常のデータマージで迷わず安全に使い分けができます。

必要に応じてネストの一部だけを個別にマージする工夫も取り入れてみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!