Pythonの辞書(dict)を結合する方法

【Python】辞書(dict)を結合する方法

公開: 更新:
CodeCampが提供するDX人材育成が可能なプログラミングやITが学べる公開講座

Pythonで辞書(dict)を結合する場面は、設定値のマージや複数のデータソースの統合など、実務で頻繁に登場します。しかし、手法ごとに「元の辞書を書き換えるかどうか」や「Python バージョンの制約」が異なるため、適切な選択を誤るとバグの原因です。

特に、破壊的な更新(元の辞書を直接変更する操作)は、グローバルな設定辞書を意図せず汚染してしまうリスクがあります。この問題を知らずに使い続けると、テストは通るのに本番環境で挙動が変わる、という原因の特定しにくいバグを引き起こします。

この記事では、Pythonで辞書を結合する代表的な3つの手法を具体的なサンプルコードとともに解説し、それぞれの特性と使い分けを明確にします。さらに、副作用を避ける安全な結合方法やネストされた辞書の再帰的マージまで解説しているので、初学者から実務エンジニアまで幅広く活用できます。



Pythonの辞書(dict)を結合する方法

Pythonで辞書を結合する方法は、主に以下の3つがあります。それぞれ、元の辞書を変更するかどうかや対応するPythonのバージョンが異なります。

  1. update()メソッドで結合する
  2. アンパック演算子(**)で結合する
  3. マージ演算子(|)で結合する

3つの手法はいずれも辞書のキーと値をまとめる目的で使いますが、破壊的(元の辞書を書き換える)か非破壊的(新しい辞書を生成する)かという点で大きく異なります。重複するキーがある場合、後から指定した値が優先される「後勝ち」の原則が適用されます。

それでは各手法について、詳しく解説していきます。

update()メソッドで結合する

update()メソッドは、辞書オブジェクトが持つ組み込みメソッドで、引数に渡した辞書・マッピング・キー値ペアのイテラブルのキーと値を呼び出し元の辞書に追加します。すでに同じキーが存在する場合は、引数側の値で上書きされます。

以下のコードは、d1d2の内容をマージする基本的な使い方です。

d1 = {"a": 1, "b": 2}
d2 = {"b": 99, "c": 3}

d1.update(d2)

print(d1)  # {'a': 1, 'b': 99, 'c': 3}
print(d2)  # {'b': 99, 'c': 3}

上記のコードでは、d1"b"キーがd2側の値99で上書きされています。d2自体は変化しませんが、d1は元の辞書が直接書き換えられる点に注意が必要です。

update()は破壊的なメソッドであるため、関数の引数として受け取った辞書やグローバルな設定辞書に対して使うと呼び出し元に影響を与えます。元の辞書を保持したまま結合した結果が必要な場合は、後述のアンパック演算子やマージ演算子を使うことを検討してください。

Update the dictionary with the key/value pairs from mapping or iterable and kwargs, overwriting existing keys. Return None.

出典:Python公式ドキュメント - Built-in Types: dict.update()
項目 内容
破壊的か はい(呼び出し元の辞書が変更される)
対応バージョン Python 3.x 全バージョン対応
戻り値 None(結果は呼び出し元の辞書に反映)

戻り値がNoneである点も見落としやすいポイントです。merged = d1.update(d2)とするとmergedNoneになるため、新しい辞書として受け取ろうとする用途には使えません。

アンパック演算子(**)で結合する

アンパック演算子(**)を使うと、辞書リテラル内で複数の辞書を展開し、新しい辞書として生成できます。辞書リテラル内で使う**は、辞書のキーと値をその場に展開して新しい辞書を構築する記法です。

なお、同じ**が関数呼び出しでキーワード引数の展開(func(**d))にも使われますが、辞書リテラル内での展開({**d})とは用途が異なります。

以下のコードは、d1d2をアンパックして、新しい辞書mergedを生成する例です。

d1 = {"a": 1, "b": 2}
d2 = {"b": 99, "c": 3}

merged = {**d1, **d2}

print(merged)  # {'a': 1, 'b': 99, 'c': 3}
print(d1)      # {'a': 1, 'b': 2}  ← 変更されない
print(d2)      # {'b': 99, 'c': 3}  ← 変更されない

d1d2はどちらも変更されず、結合結果は新しい辞書mergedに格納されます。これは非破壊的なマージであり、元の辞書を保護したい場面で安全に使えます。

キーの重複がある場合、右側(後に書いた)辞書の値が優先されます。{**d1, **d2}ではd2側の値が採用され、{**d2, **d1}ではd1側の値が採用されるため、記述の順序によって結果が変わる点を把握しておきましょう。

項目 内容
破壊的か いいえ(元の辞書は変更されない)
対応バージョン Python 3.5以降
戻り値 新しい辞書(dictオブジェクト)

辞書リテラル内での**アンパックはPython 3.5で追加されたため、Python 3.5〜3.8のプロジェクトで非破壊的なマージを行う際の標準的な選択肢です。Python 3.9以降であれば、後述のマージ演算子(|)がより簡潔な選択肢となります。

なお、Python 3.9以降でも{**d1, **d2}は引き続き使用できます。


Python研修一覧はこちら

目的に合うPython研修を一覧形式から探したい方は、ぜひご利用ください。

Python研修を比較する

Java研修一覧はこちら

目的に合うJava研修を一覧形式から探したい方は、ぜひご利用ください。

Java研修を比較する

PHP研修一覧はこちら

目的に合うPHP研修を一覧形式から探したい方は、ぜひご利用ください。

PHP研修を比較する

新入社員研修

目的に合う新入社員研修を一覧形式から探したい方は、ぜひご利用ください。

新入社員研修を比較する

全ての研修からも探したい方はこちら

マージ演算子(|)で結合する

Python 3.9で追加されたマージ演算子(|)は、2つの辞書を結合して新しい辞書を返す演算子です。アンパック演算子と同様に非破壊的なマージを実現しますが、記述がより簡潔で意図が明確に伝わります。

以下のコードは、|演算子を使った基本的な結合例です。

d1 = {"a": 1, "b": 2}
d2 = {"b": 99, "c": 3}

merged = d1 | d2

print(merged)  # {'a': 1, 'b': 99, 'c': 3}
print(d1)      # {'a': 1, 'b': 2}  ← 変更されない
print(d2)      # {'b': 99, 'c': 3}  ← 変更されない

d1 | d2d1を基準にd2の内容をマージした新しい辞書を返します。重複するキーがある場合は右側(d2)の値が優先される「後勝ち」の原則に従います。

また、累積代入演算子|=を使うと、既存の辞書をインプレースで更新できます。

d1 = {"a": 1, "b": 2}
d2 = {"b": 99, "c": 3}

d1 |= d2  # d1 自体を更新する(インプレース更新)

print(d1)  # {'a': 1, 'b': 99, 'c': 3}

|=はインプレース更新を行い、PEP 584でaugmented assignment(累積代入演算子)として定義されています。update()と実質的な動作(呼び出し元の辞書が変わる)は同じですが、|演算子が非破壊的(新しい辞書を返す)であるのに対し、|=は破壊的(既存辞書をインプレース更新)である点を区別しておきましょう。

Dict union will return a new dict consisting of the left operand merged with the right operand, each of which must be a dict (or an instance of a dict subclass). If a key appears in both operands, the last-seen value (i.e. that from the right-hand operand) wins.

出典:PEP 584 – Add Union Operators To dict
演算子 破壊的か 対応バージョン
d1 | d2 いいえ Python 3.9以降
d1 |= d2 はい(インプレース更新) Python 3.9以降

プロジェクトのPythonバージョンが3.9以上であり、新しい辞書を作成したい場合は、|演算子が最も読みやすく、意図の伝わりやすい選択肢となります。既存辞書を更新したい場合や Python 3.8以下への対応が必要な場合は、update()やアンパック演算子を検討してください。

Pythonのdict結合で副作用を避ける方法

辞書の結合において、実務で特に注意が必要なのが「副作用(サイドエフェクト)」と呼ばれる問題です。ここでは副作用を「元の辞書オブジェクトを意図せず書き換えてしまうこと」と定義します。

以下の2つのトピックを解説します。

  1. 元の辞書を変更せずに結合する(破壊的操作の回避)
  2. ネストされた辞書を再帰的に結合する(ネスト構造のデータロス防止)

それぞれ異なる問題に対処する手法であり、コードの安全性と保守性を高めることができます。

それでは各項目について、詳しく解説していきます。

元の辞書を変更せずに結合する

前述の通り、update()メソッドは元の辞書オブジェクトを直接書き換える「破壊的な」操作です。たとえばプログラム冒頭で定義した定数辞書にupdate()を適用すると、その辞書オブジェクトを参照しているすべての箇所に影響を与えます。

このような意図しない状態変更(副作用)は、設定の混入や予期しない挙動を引き起こす原因となります。

元の辞書を保護するには、非破壊的な結合手法を使う必要があります。Python 3.9以降であれば|演算子、Python 3.5〜3.8であれば辞書アンパック{**d1, **d2}が推奨される選択肢です。

以下のコードは、定数辞書DEFAULT_CONFIGを変更せずにユーザー設定をマージする実装例です。

# 定数として定義した既定設定(書き換えてはいけない)
DEFAULT_CONFIG = {"timeout": 30, "retry": 3, "debug": False}

# ユーザーが指定した設定(一部のキーのみ上書き)
user_config = {"timeout": 60, "debug": True}

# 非破壊的な結合: Python 3.9以降は | 演算子を使用
merged_py39 = DEFAULT_CONFIG | user_config
print(merged_py39)
# {'timeout': 60, 'retry': 3, 'debug': True}

# 非破壊的な結合: Python 3.5〜3.8はアンパック演算子を使用
merged_py38 = {**DEFAULT_CONFIG, **user_config}
print(merged_py38)
# {'timeout': 60, 'retry': 3, 'debug': True}

# 元の辞書が変更されていないことを確認
print(DEFAULT_CONFIG)
# {'timeout': 30, 'retry': 3, 'debug': False}  ← 変化なし

どちらの手法も新しい辞書オブジェクトを生成するため、DEFAULT_CONFIGはコード実行後も初期値を保ちます。重複するキーは右側(user_config)の値が採用される「後勝ち」の原則に従います。

なお、update()を使う必要がある場合は、事前にbase = DEFAULT_CONFIG.copy()でコピーを作成し、コピー先を更新する形で副作用を回避できます。インプレース更新が必要な場面では、このパターンを習慣化することが安全な実装の基本となります。

ネストされた辞書を再帰的に結合する

フラットな辞書であれば|演算子やアンパック演算子で十分ですが、階層構造を持つネストされた辞書(入れ子辞書)を結合する場合は問題が生じます。標準的な結合手法では、ネストしたキーに対して「値ごと上書き」が発生し、子辞書の内容が丸ごと失われてしまうためです。

この問題を解決するには、再帰的マージ(Deep Merge)と呼ばれる手法が必要です。再帰的マージとは、ネストした辞書のキーを走査しながら、両方に存在するキーについては子辞書を再帰的にマージする処理を繰り返す手法です。

以下のコードは、再帰関数を用いたDeep Mergeの実装例です。

def deep_merge(base: dict, override: dict) -> dict:
    """
    ネストされた辞書を再帰的にマージする関数。
    override の値を優先し、base を変更せずに新しい辞書を返す。
    """
    result = base.copy()
    for key, value in override.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            # 両方がdictの場合は再帰的にマージ
            result[key] = deep_merge(result[key], value)
        else:
            # それ以外はoverrideの値で上書き
            result[key] = value
    return result


# ネストされた設定辞書
base_config = {
    "database": {"host": "localhost", "port": 5432, "name": "mydb"},
    "cache": {"ttl": 300, "backend": "redis"},
    "debug": False,
}

override_config = {
    "database": {"host": "prod-server", "timeout": 10},
    "debug": True,
}

# 通常の結合(NG: databaseキーが丸ごと上書きされる)
shallow_merge = base_config | override_config
print(shallow_merge["database"])
# {'host': 'prod-server', 'timeout': 10}  ← port と name が失われる

# 再帰的マージ(OK: portとnameを保持しつつhostとtimeoutを追加)
merged = deep_merge(base_config, override_config)
print(merged["database"])
# {'host': 'prod-server', 'port': 5432, 'name': 'mydb', 'timeout': 10}
print(merged["debug"])
# True
print(base_config)  # 元の辞書は変更されていない
# {'database': {'host': 'localhost', ...}, 'cache': {...}, 'debug': False}

deep_merge()関数は、両方の辞書に存在するキーの値がいずれもdictである場合に限り、再帰呼び出しによって子辞書をマージします。それ以外のケースではoverride側の値で単純に上書きするため、スカラー値には通常の後勝ちルールが適用されます。

なお、base側にのみ存在するキーはresult = base.copy()の時点でそのまま引き継がれます。

関数内で使用しているbase.copy()はシャローコピー(浅いコピー)です。辞書型の値については再帰的に独立したコピーが作られますが、リストなどのミュータブルな値については参照が共有されます。

このため「完全に独立した新しい辞書が生成される」とは限らない点に注意してください。

完全な独立コピーが必要な場合はcopy.deepcopy()の使用を検討してください。設定ファイルの多段マージやユーザー設定とデフォルト値のオーバーレイが必要な場面で、このパターンを活用してください。


Python基礎・実践(Django)

企業・法人向けのPython研修では、基礎から応用まで体系的に学べます。

Python研修の詳細

DX社員研修

企業・法人向けのDX研修では、実務に繋がるリスキリングでITレベルを向上させます。

DX研修の詳細

Javaエンジニア育成研修

企業・法人向けのJavaエンジニア育成研修では、Javaの基礎から応用まで確実に習得できます。

Java研修の詳細

新卒・新入社員向け研修

企業・法人に新入社員・新卒社員に向けたプログラミング研修を提供しています。

新入社員研修の詳細

コードキャンプのIT研修を全て見る

Pythonの辞書の結合に関するよくある質問

3つ以上の辞書を一度に結合できますか?

はい、3つ以上の辞書でも一度に結合できます。Python 3.9以降であればd1 | d2 | d3のようにパイプ演算子を連結し、それ以前のバージョンでは{**d1, **d2, **d3}のようにアンパック演算子を並べる方法が有効です。

いずれの方法でも左から右の順に処理され、同一キーが複数の辞書に存在する場合は右側の値が優先されます。

キーが重複したときに上書きを防ぐ方法はありますか?

上書きを完全に防ぐには、マージ前にset(d1) & set(d2)で重複キーを検出して、マージ戦略を明示的に決める必要があります。左側を優先したい場合は{**d2, **d1}のように記述順を逆にするアプローチが有効です。

なお、dict(**d1, **d2)でも重複キーを検出できますが、キーが文字列以外の辞書には使えません。

ChainMapと辞書の結合はどう違いますか?

collections.ChainMapは、複数の辞書を実際にはコピーせず仮想的に1つの辞書として参照する仕組みです。|演算子やアンパック演算子と異なり元の辞書への参照を保持するため、元の辞書への変更がChainMapにも即座に反映されます。

メモリ効率を優先する場面や読み取り専用の論理的な統合にはChainMapが有効ですが、変更可能な独立した新しい辞書が必要な場合は通常の結合手法を選んでください。

※上記コンテンツの内容やソースコードはAIで確認・デバッグしておりますが、間違いやエラー、脆弱性などがある場合は、コメントよりご報告いただけますと幸いです。

ITやプログラミングに関するコラム


ITやプログラミングに関するニュース

ブログに戻る

コメントを残す

コメントは公開前に承認される必要があることにご注意ください。

企業・法人向けのIT・プログラミング・生成AI研修を探す、比較する - IT・プログラミングを知って学べるコネクトメディア CodeCampが提供するDX人材育成が可能なプログラミングやITが学べる公開講座 - IT・プログラミングを知って学べるコネクトメディア コードキャンプが提供する無料で学べるプログラミングスクール講座 - IT・プログラミングを知って学べるコネクトメディア コードキャンプDX人材育成研修 - IT・プログラミングを知って学べるコネクトメディア 3.5日の研修で、年間1,600時間の削減効果が見込まれる。東京きらぼしフィナンシャルグループのDX人材育成事例 - IT・プログラミングを知って学べるコネクトメディア 配属3ヶ月で30%の生産性向上を実現するいよぎんコンピュータサービスの新人研修に迫る - IT・プログラミングを知って学べるコネクトメディア 金融業界の業務効率化を加速するニッセイアセットマネジメントの生成AI×GAS活用研修事例 - IT・プログラミングを知って学べるコネクトメディア 【製造業のDX人材育成事例】デジタル人材の即戦力化を実現する、日本ガイシ株式会社の異動者向オンボーディング研修 - ITやプログラミングを知って学べるコネクトメディア フューチャーアーキテクト株式会社が実現した新入社員向けIT研修プログラムでタスクフォース制度が主体的な学びと成長を生み出す - IT・プログラミングを知って学べるコネクトメディア コードキャンプDX人材育成研修 - IT・プログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/【IT新入社員研修】オンラインとオフラインの最適バランスを実現したFutureOneの導入事例 - IT・プログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/【新入社員研修】柔軟なハイブリッド型Java研修で実現した新卒20名の成長と成果|サークレイス株式会社 - ITやプログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/現場により近いところにデジタルを根付かせるDX基礎講座研修|株式会社ブリヂストン - ITやプログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/業務の効率化・DX推進に向けたIT人材育成への第一歩|株式会社カナエ - ITやプログラミングを知って学べるコネクトメディア 企業・法人向けのIT・プログラミング研修 - ITやプログラミングを知って学べるコネクトメディア

新着記事

対象者別で探す

子供(小学生・中学生・高校生)向け
プログラミング教室検索する

子供(小学生・中学生・高校生)がロボットやプログラミング言語を学ぶことができるオフラインからオンラインスクールを検索、比較することが可能です。

子供(小学生・中学生・高校生)
プログラミング教室検索する

ITやプログラムなどの
最新情報を検索する

日々、新しいITやプログラミング言語の情報が流れていきますが、特定の情報を時系列でニュースやコラムを確認することができます。

ITやプログラムなどの
最新情報を検索する