Pythonの辞書の結合(マージ)とは、2つ以上の辞書のキーと値を1つにまとめる操作で、元の辞書を書き換えるなら update か |=、新しい辞書を作るなら | 演算子か {**a, **b} を使います。 どの書き方でも、同じキーが両方にあるときは「後から渡したほう(右側)」の値が残ります。既にある値を残したいときは setdefault、辞書をコピーせずに重ねて見たいときは ChainMap が合います。

この記事では、6つの方法を 「元の辞書を書き換えるか」「キー重複時にどちらが残るか」「対応バージョン」 の3つの軸で比較します。すべてのコードは Python 3.11.15 と 3.14.7 で実行し、出力をそのまま載せました(| 演算子は 3.8.20 と 3.10.18 でも実行しています)。ネストした辞書が丸ごと置き換わる様子、コピーして使える再帰マージ関数、上書きしない結合、リスト値を足し合わせる結合まで、ひととおり確認できます。

辞書の結合・マージ方法の早見表

まずは結論の表です。迷ったら「新しい辞書がほしい → a | b」「a 自体を更新したい → a.update(b)」 と覚えておくと、ほとんどの場面で用が足ります。

書き方 元の辞書 a キー重複時に残る値 戻り値 対応バージョン 向いている場面
a.update(b) 書き換える b(後から渡した側) None すべての Python 3 a をその場で更新する
a | b そのまま b(右側) 新しい辞書 3.9 以降 新しい辞書を1行で作る
a |= b 書き換える b(右側) (代入文) 3.9 以降 update を演算子で書く
{**a, **b} そのまま b(後ろ側) 新しい辞書 3.5 以降 3.8 以前でも動かす、キーを足しながら結合する
a.setdefault(k, v) キーがないときだけ書き換える a(既にある値) 最終的な値 すべての Python 3 上書きせずに初期値を入れる
ChainMap(a, b) そのまま(コピーしない) a(先に渡した側) ChainMap 3.3 以降 設定の優先順位を重ねて参照する

やりたいことから探すときは、次の逆引き表を使ってください。

やりたいこと 書き方 結果のポイント
2つの辞書から新しい辞書を作る c = a | b a も b も変わらない
a に b の内容を取り込む a.update(b) a が変わる。戻り値は None
3つ以上をまとめる a | b | c または {**a, **b, **c} 右にあるものほど優先
既にあるキーは上書きしない defaults | user または setdefault のループ 優先したい辞書を右に置く
ネストした辞書も中身ごとマージする 再帰マージ関数(後述の deep_merge) 標準の方法はすべて浅いマージ
リストの値を連結する out.setdefault(k, []).extend(v) 上書きではなく足し合わせる
数値を合計する Counter(a) + Counter(b) 0 以下の値は消える点に注意
コピーせずに優先順位つきで参照する ChainMap(user, defaults) 元の辞書の変更が反映される

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python辞書の結合・マージ updateと|演算子とsetdefault」

確認に使った環境と入力データ

この記事の実行結果は、次の環境で得たものです。

項目 内容
主な実行環境 Python 3.11.15(Linux)
追加で実行した版 Python 3.14.7(全コード、出力は 3.11 と同じ)、3.10.18 と 3.8.20(| 演算子の対応確認)
使うモジュール 標準ライブラリのみ(collections、copy、functools)

多くの例で、次の2つの辞書を使います。age というキーが両方にある点がポイントです。

a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}

updateで元の辞書を書き換えて結合する

a.update(b) は、a 自体に b のキーと値を取り込みます。 同じキーがあれば b の値で上書きされ、b は変わりません。

a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}

result = a.update(b)
print(result)
print(a)
print(b)
None
{'name': 'taro', 'age': 21, 'city': 'Tokyo'}
{'age': 21, 'city': 'Tokyo'}

age は b の 21 に変わり、city が追加されました。ここで押さえておきたいのは update の戻り値が None であることです。次のように書くと、結合結果ではなく None が変数に入ります。

merged = {"a": 1}.update({"b": 2})
print(merged)
None

結合した結果を別の変数で受け取りたいときは、後述の a | b を使うか、merged = a.copy() のあとに merged.update(b) と2行で書きます。

updateが受け取れる引数

update は辞書以外も受け取れます。キーワード引数や、(キー, 値) のペアの並びを渡せるので、1〜2個のキーを足すときにも便利です。

d = {"a": 1}
d.update(b=2, c=3)
print(d)

d = {"a": 1}
d.update([("b", 2)])
print(d)

d = {"a": 1}
d.update(["ab", "cd"])
print(d)
{'a': 1, 'b': 2, 'c': 3}
{'a': 1, 'b': 2}
{'a': 'b', 'c': 'd'}

3つ目は少し意外な結果です。2文字の文字列は「長さ2の並び」として扱われるため、"ab" がキー a・値 b のペアになり、元の a の値 1 が b に上書きされます。

update に文字列のリストを渡すと、意図しないキーができることがあるので、渡す値が辞書かペアの並びかを確かめてから使うと安心です。

縦棒の演算子で新しい辞書を作る(Python 3.9以降)

a | b は、a と b を結合した新しい辞書を返します。 a も b も変わらないので、元のデータを残したいときに向いています。

a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}

c = a | b
print(c)
print(a)
print(b | a)
{'name': 'taro', 'age': 21, 'city': 'Tokyo'}
{'name': 'taro', 'age': 20}
{'age': 20, 'city': 'Tokyo', 'name': 'taro'}

a | b と b | a では結果が変わります。値は右側が優先、キーの並び順は左側が基準です。b | a では age が a の 20 になり、並びは b の順(age、city)のあとに a だけにある name が続きます。

3.8以前で実行するとどうなるか

縦棒の演算子(|)は Python 3.9 で追加されました。3.8.20 で同じコードを実行すると、次のように止まります。

import sys
print(sys.version.split()[0])
a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}
print({**a, **b})
try:
    print(a | b)
except TypeError as e:
    print("TypeError:", e)
3.8.20
{'name': 'taro', 'age': 21, 'city': 'Tokyo'}
TypeError: unsupported operand type(s) for |: 'dict' and 'dict'

3.10.18 では a | b も {**a, **b} も同じ結果になりました。3.8 以前でも動かす必要があるコードでは {**a, **b} を選ぶと、どの版でも同じ結果が得られます。

累積代入の |= は update と同じ働き

a |= b は a 自体を書き換えます。 新しい辞書は作られず、同じオブジェクトのまま内容が更新されます。

a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}

before = a
a |= b
print(a)
print(before is a)
{'name': 'taro', 'age': 21, 'city': 'Tokyo'}
True

before is a が True なので、a は作り直されず、その場で更新されたことがわかります。関数の引数で受け取った辞書に |= を使うと、呼び出し元の辞書も変わる点は update と同じです。

| と |= で受け取れる型の違い

| は右側も辞書である必要がありますが、|= は update と同じく (キー, 値) のペアの並びも受け取ります。

try:
    print({"a": 1} | [("b", 2)])
except TypeError as e:
    print("TypeError:", e)

d = {"a": 1}
d |= [("b", 2)]
print(d)
TypeError: unsupported operand type(s) for |: 'dict' and 'list'
{'a': 1, 'b': 2}

アンパックで結合する {**a, **b}(Python 3.5以降)

{**a, **b} は、辞書リテラルの中に a と b を展開して新しい辞書を作る書き方です。結果は a | b と同じで、後ろに書いたものが優先されます。

a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}

print({**a, **b})
print({**a, **b, "age": 99})
print({**a, "role": "admin"})
{'name': 'taro', 'age': 21, 'city': 'Tokyo'}
{'name': 'taro', 'age': 99, 'city': 'Tokyo'}
{'name': 'taro', 'age': 20, 'role': 'admin'}

結合しながら個別のキーを足したり上書きしたりできるのが、この書き方の強みです。2行目では、b の 21 よりもさらに後ろに書いた 99 が残っています。

dict(a, **b) は文字列キー専用

似た書き方に dict(a, **b) があります。こちらは b をキーワード引数として渡すため、b のキーが文字列でないとエラーになります。

a = {"name": "taro", "age": 20}

print({**a, **{1: "x"}})
try:
    print(dict(a, **{1: "x"}))
except TypeError as e:
    print("TypeError:", e)
{'name': 'taro', 'age': 20, 1: 'x'}
TypeError: keywords must be strings

キーの型を気にせず使える {**a, **b} か a | b を選んでおくと、後からキーの種類が増えても同じコードで対応できます。

キーが重複したときの結果を並べて確認する

ここまでの方法を、同じ入力で並べて実行します。「どちらの値が残るか」は、渡す順番で決まります。

from collections import ChainMap

a = {"name": "taro", "age": 20}
b = {"age": 21, "city": "Tokyo"}

print("a | b        ", a | b)
print("b | a        ", b | a)
print("{**a, **b}   ", {**a, **b})
print("{**b, **a}   ", {**b, **a})
print("ChainMap(a,b)", dict(ChainMap(a, b)))
print("ChainMap(b,a)", dict(ChainMap(b, a)))

c = a.copy()
c.update(b)
print("update       ", c)

d = a.copy()
for k, v in b.items():
    d.setdefault(k, v)
print("setdefault   ", d)
a | b         {'name': 'taro', 'age': 21, 'city': 'Tokyo'}
b | a         {'age': 20, 'city': 'Tokyo', 'name': 'taro'}
{**a, **b}    {'name': 'taro', 'age': 21, 'city': 'Tokyo'}
{**b, **a}    {'age': 20, 'city': 'Tokyo', 'name': 'taro'}
ChainMap(a,b) {'age': 20, 'city': 'Tokyo', 'name': 'taro'}
ChainMap(b,a) {'name': 'taro', 'age': 21, 'city': 'Tokyo'}
update        {'name': 'taro', 'age': 21, 'city': 'Tokyo'}
setdefault    {'name': 'taro', 'age': 20, 'city': 'Tokyo'}
方法 優先される側 覚え方
a | b、{**a, **b}、a.update(b) b(後・右) 後から書いたものが上書きする
ChainMap(a, b) a(先・左) 先に見つかったものを返す
setdefault のループ a(既にある値) キーがないときだけ入れる

ChainMap だけ優先順が逆(先に渡した側が残る) なので、他の方法から書き換えるときは引数の順番を入れ替えます。

setdefaultで上書きせずに追加する

d.setdefault(key, default) は、キーがなければ default を入れ、既にあれば何もしません。 どちらの場合も、最終的にそのキーに入っている値を返します。

conf = {"host": "localhost"}

print(conf.setdefault("host", "example.com"))
print(conf.setdefault("port", 5432))
print(conf)
localhost
5432
{'host': 'localhost', 'port': 5432}

host は既にあるので localhost のまま、port はなかったので 5432 が入りました。

値がNoneのキーは「ある」と判定される

setdefault が見るのは キーの有無 です。値が None でもキーがあれば上書きされません。

d = {"k": None}
print(d.setdefault("k", 1))
print(d)
None
{'k': None}

「値が None のときも初期値を入れたい」場合は、if d.get("k") is None: d["k"] = 1 のように値を見て判定します。

上書きしない結合の3つの書き方

利用者の設定 user に、足りない項目だけ既定値 defaults から補う例です。既にある theme は残し、ないキーだけ追加します。

defaults = {"theme": "light", "lang": "ja", "page_size": 20}

# 方法1: setdefault のループ(user を書き換える)
user = {"theme": "dark"}
for key, value in defaults.items():
    user.setdefault(key, value)
print(user)

# 方法2: 優先したい辞書を右に置く(新しい辞書を作る)
user = {"theme": "dark"}
print(defaults | user)
print({**defaults, **user})

# 方法3: ないキーだけ update に渡す(user を書き換える)
user = {"theme": "dark"}
user.update((k, v) for k, v in defaults.items() if k not in user)
print(user)
{'theme': 'dark', 'lang': 'ja', 'page_size': 20}
{'theme': 'dark', 'lang': 'ja', 'page_size': 20}
{'theme': 'dark', 'lang': 'ja', 'page_size': 20}
{'theme': 'dark', 'lang': 'ja', 'page_size': 20}

4行とも同じ内容になります。defaults | user は右側の user が優先されるので theme は dark のまま、キーの並びは左側の defaults の順(theme、lang、page_size)です。

書き方 user を書き換えるか 選ぶ目安
setdefault のループ 書き換える user をそのまま使い続けたい
defaults | user 書き換えない 元の2つを残して結果だけほしい(3.9以降)
ないキーだけ update 書き換える 条件を足したい(特定のキーだけ補う など)

ChainMapでコピーせずに重ねて参照する

ChainMap は、複数の辞書を「重ねて1つに見せる」入れ物です。 値をコピーしないので、作成が速く、元の辞書を後から変更すると参照結果にも反映されます。検索は先に渡した辞書から順に行われます。

from collections import ChainMap

defaults = {"theme": "light", "lang": "ja", "page_size": 20}
user = {"theme": "dark"}

settings = ChainMap(user, defaults)
print(settings["theme"], settings["lang"])
print(settings)
print(dict(settings))

# 元の辞書を変えると、参照結果も変わる
defaults["lang"] = "en"
print(settings["lang"])
dark ja
ChainMap({'theme': 'dark'}, {'theme': 'light', 'lang': 'ja', 'page_size': 20})
{'theme': 'dark', 'lang': 'ja', 'page_size': 20}
en

theme は先頭の user から、lang は user にないので defaults から取得されました。dict(settings) と書けば、その時点の内容を普通の辞書として取り出せます。

書き込みと削除は先頭の辞書だけに届く

Python 3.14 の公式ドキュメント(collections の ChainMap の項)には、検索は順にたどり、書き込み・更新・削除は最初のマッピングだけに作用すると書かれています。実行して確かめます。

from collections import ChainMap

defaults = {"theme": "light", "lang": "ja", "page_size": 20}
user = {"theme": "dark"}
settings = ChainMap(user, defaults)

settings["page_size"] = 50
print(user)
print(defaults)

try:
    del settings["lang"]
except KeyError as e:
    print("KeyError:", e)

print(len(settings), list(settings))
{'theme': 'dark', 'page_size': 50}
{'theme': 'light', 'lang': 'ja', 'page_size': 20}
KeyError: "Key not found in the first mapping: 'lang'"
3 ['theme', 'lang', 'page_size']

page_size への代入は user に入り、defaults は 20 のままです。defaults にしかない lang を削除しようとすると KeyError になります。既定値の辞書を守りながら、上書き分だけを先頭の辞書にためられるのが ChainMap の特長です。

一時的な上書きに便利な new_child と parents

new_child() で先頭に辞書を1枚足し、parents で先頭を外した ChainMap を得られます。

from collections import ChainMap

settings = ChainMap({"theme": "dark"}, {"theme": "light", "lang": "ja"})
temp = settings.new_child({"theme": "blue"})
print(temp["theme"])
print(temp.parents["theme"])
print(settings["theme"])
blue
dark
dark

もとの settings は変わらないので、「この処理の間だけ設定を変える」といった使い方に合います。

ChainMap と dict の使い分け

観点 a | b(辞書を作る) ChainMap(b, a)(重ねて見る)
コピー する(浅いコピー) しない
元の辞書の変更 結果に反映されない 反映される
優先される側 右側 先に渡した側
書き込み先 新しい辞書 先頭の辞書
向いている場面 結合結果を保存・受け渡しする コマンドライン引数、環境変数、既定値のように優先順位を重ねる

ネストした辞書は浅いマージで丸ごと置き換わる

ここが一番つまずきやすい点です。update、|、|=、{**a, **b}、ChainMap はすべて「浅いマージ」 で、いちばん外側のキーだけを見ます。値が辞書であっても、中身を混ぜ合わせることはしません。

base = {
    "db": {"host": "localhost", "port": 5432, "opt": {"ssl": False}},
    "debug": False,
}
over = {"db": {"port": 6543}, "debug": True}

print(base | over)
{'db': {'port': 6543}, 'debug': True}

db の中の port だけを変えたかったのに、db 全体が {'port': 6543} に置き換わり、host と opt がなくなりました。 {**base, **over} や base.update(over) でも結果は同じです。

浅いコピーは内側の辞書を共有する

もう1つ知っておきたいのが、a | b で作った新しい辞書は 内側の辞書やリストを元の辞書と共有している ことです。

base = {"db": {"host": "localhost", "port": 5432}}

merged = base | {}
merged["db"]["port"] = 1

print(base["db"]["port"])
print(merged["db"] is base["db"])
1
True

merged 側で内側の値を変えると、base 側も変わります。外側は別の辞書でも、内側は同じオブジェクトを指しているためです。内側まで独立させたいときは、次の再帰マージ関数のように copy.deepcopy を使います。

再帰マージ関数でネストした辞書を結合する

内側の辞書までたどって結合する関数です。標準ライブラリだけで動き、そのままコピーして使えます。元の辞書は変更せず、新しい辞書を返します。

import copy


def deep_merge(base, override):
    """base に override を再帰的に重ねた新しい辞書を返す(元の辞書は変更しない)"""
    result = copy.deepcopy(base)
    for key, value in override.items():
        if isinstance(result.get(key), dict) and isinstance(value, dict):
            result[key] = deep_merge(result[key], value)
        else:
            result[key] = copy.deepcopy(value)
    return result


base = {
    "db": {"host": "localhost", "port": 5432, "opt": {"ssl": False}},
    "debug": False,
}
over = {"db": {"port": 6543}, "debug": True}

merged = deep_merge(base, over)
print(merged)
print(base)
print(merged["db"] is base["db"])
{'db': {'host': 'localhost', 'port': 6543, 'opt': {'ssl': False}}, 'debug': True}
{'db': {'host': 'localhost', 'port': 5432, 'opt': {'ssl': False}}, 'debug': False}
False

db の中は port だけが 6543 に変わり、host と opt は残りました。base は元のまま、内側の辞書も別のオブジェクトになっています。

型が違うとき・リストのときの動き

この関数は 両方が辞書のときだけ再帰 し、それ以外は override 側の値で置き換えます。

print(deep_merge({"a": {"x": 1}}, {"a": 5}))
print(deep_merge({"a": 5}, {"a": {"x": 1}}))
print(deep_merge({"a": [1]}, {"a": [2]}))
{'a': 5}
{'a': {'x': 1}}
{'a': [2]}
base の値 override の値 結果
辞書 辞書 中身を再帰的にマージ
辞書 辞書以外 override の値に置き換え
辞書以外 辞書 override の辞書に置き換え
リスト リスト override のリストに置き換え(連結はしない)

リストも連結する拡張版

リスト同士は連結したい場合は、分岐を1つ足します。

import copy


def deep_merge_ext(base, override):
    """辞書は再帰マージ、リストは連結、それ以外は override で置き換える"""
    result = copy.deepcopy(base)
    for key, value in override.items():
        current = result.get(key)
        if isinstance(current, dict) and isinstance(value, dict):
            result[key] = deep_merge_ext(current, value)
        elif isinstance(current, list) and isinstance(value, list):
            result[key] = current + copy.deepcopy(value)
        else:
            result[key] = copy.deepcopy(value)
    return result


x = {"tags": ["a"], "db": {"hosts": ["h1"]}}
y = {"tags": ["b"], "db": {"hosts": ["h2"], "port": 1}}
print(deep_merge_ext(x, y))
{'tags': ['a', 'b'], 'db': {'hosts': ['h1', 'h2'], 'port': 1}}

「リストは置き換えるのか、連結するのか」は用途で決まるので、関数の説明文(docstring)に方針を書いておくと、あとで読む人が迷いません。

リストの値を足し合わせて結合する

同じキーの値がリストのとき、a | b では後ろのリストで置き換わります。両方の要素を残したいときは、setdefault と extend を組み合わせます。

x = {"fruit": ["apple"], "veg": ["carrot"]}
y = {"fruit": ["banana"], "nut": ["almond"]}

# そのまま結合すると、fruit は y のリストに置き換わる
print(x | y)

# setdefault + extend で足し合わせる
out = {}
for d in (x, y):
    for key, values in d.items():
        out.setdefault(key, []).extend(values)
print(out)
print(x)
{'fruit': ['banana'], 'veg': ['carrot'], 'nut': ['almond']}
{'fruit': ['apple', 'banana'], 'veg': ['carrot'], 'nut': ['almond']}
{'fruit': ['apple'], 'veg': ['carrot']}

out.setdefault(key, []) は「キーがなければ空のリストを入れ、そのリストを返す」ので、続けて .extend(values) と書けます。元の x は変わっていません。

defaultdict と内包表記でも書ける

from collections import defaultdict

x = {"fruit": ["apple"], "veg": ["carrot"]}
y = {"fruit": ["banana"], "nut": ["almond"]}

merged = defaultdict(list)
for d in (x, y):
    for key, values in d.items():
        merged[key].extend(values)
print(dict(merged))

print({key: x.get(key, []) + y.get(key, []) for key in x | y})
{'fruit': ['apple', 'banana'], 'veg': ['carrot'], 'nut': ['almond']}
{'fruit': ['apple', 'banana'], 'veg': ['carrot'], 'nut': ['almond']}
書き方 特徴
out.setdefault(k, []).extend(v) 追加の import がいらない。3つ以上の辞書にもそのまま使える
defaultdict(list) ループの中が短くなる。最後に dict() で普通の辞書に戻せる
内包表記 x.get(k, []) + y.get(k, []) 2つの辞書なら1行で書ける

setdefault に元のリストをそのまま渡すと共有される

out.setdefault(key, values) のように 元のリストそのもの を初期値にすると、out と x が同じリストを指します。

x = {"fruit": ["apple"]}

out = {}
for key, values in x.items():
    out.setdefault(key, values)
out["fruit"].append("!!")
print(x["fruit"])
['apple', '!!']

out に追加したつもりが、x も変わりました。初期値は [](新しい空のリスト)にして extend で足す書き方にすると、元のデータと切り離せます。

数値を合計するなら Counter

値が数値で、同じキーを合計したいときは collections.Counter が使えます。

from collections import Counter

s1 = {"apple": 3, "banana": 1}
s2 = {"apple": 2, "cherry": 5}

print(dict(Counter(s1) + Counter(s2)))
print({key: s1.get(key, 0) + s2.get(key, 0) for key in s1 | s2})
print(dict(Counter({"a": 1}) + Counter({"a": -1})))
{'apple': 5, 'banana': 1, 'cherry': 5}
{'apple': 5, 'banana': 1, 'cherry': 5}
{}

3行目のとおり、Counter の足し算は合計が 0 以下になったキーを結果から外します。 0 や負の値も残したいときは、2行目の内包表記を使います。

3つ以上の辞書をまとめて結合する

リストに入った複数の辞書を1つにまとめる書き方です。どの方法でも、後ろにある辞書ほど優先されます(ChainMap は逆順にして渡します)。

from collections import ChainMap
from functools import reduce

dicts = [{"a": 1}, {"b": 2}, {"a": 3, "c": 4}]

# 方法1: update のループ
merged = {}
for d in dicts:
    merged.update(d)
print(merged)

# 方法2: reduce と | 演算子
print(reduce(lambda p, q: p | q, dicts, {}))

# 方法3: ChainMap(先頭が優先なので逆順に渡す)
print(dict(ChainMap(*reversed(dicts))))
{'a': 3, 'b': 2, 'c': 4}
{'a': 3, 'b': 2, 'c': 4}
{'a': 3, 'b': 2, 'c': 4}

個数が決まっているなら a | b | c や {**a, **b, **c} と並べるのが簡潔です。個数が決まっていないときは、読みやすい update のループをおすすめします。

つまずきやすいエラーと直し方

実際に出るエラー文と、直し方をまとめます。

tests = [
    ("{'a': 1} | None", lambda: {"a": 1} | None),
    ("{'a': 1}.update(None)", lambda: {"a": 1}.update(None)),
    ("{**{'a': 1}, **None}", lambda: {**{"a": 1}, **None}),
    ("{'a': 1}.update('abc')", lambda: {"a": 1}.update("abc")),
]
for label, func in tests:
    try:
        print(label, "->", func())
    except Exception as e:
        print(label, "->", type(e).__name__ + ":", e)
{'a': 1} | None -> TypeError: unsupported operand type(s) for |: 'dict' and 'NoneType'
{'a': 1}.update(None) -> TypeError: 'NoneType' object is not iterable
{**{'a': 1}, **None} -> TypeError: 'NoneType' object is not a mapping
{'a': 1}.update('abc') -> ValueError: dictionary update sequence element #0 has length 1; 2 is required
エラー文 原因 直し方
unsupported operand type(s) for |: 'dict' and 'dict' Python 3.8 以前で | を使った {**a, **b} に書き換える、または 3.9 以降で実行する
unsupported operand type(s) for |: 'dict' and 'NoneType' 右側が None a | (b or {}) のように空の辞書に置き換える
unsupported operand type(s) for |: 'dict' and 'list' 右側がペアのリスト a | dict(pairs) にする
'NoneType' object is not iterable update(None) a.update(b or {}) にする
dictionary update sequence element #0 has length 1; 2 is required update に文字列などを渡した 辞書か (キー, 値) の並びを渡す
keywords must be strings dict(a, **b) で b に文字列以外のキー {**a, **b} か a | b にする
結果が None になる c = a.update(b) と書いた c = a | b にする

None が来るかもしれない場面では、次の形が短くて読みやすいです。

a = {"x": 1}
b = None
print(a | (b or {}))
{'x': 1}

速度の目安(1,000キー同士の結合)

キーが 1,000 個の辞書2つ(うち 500 個が重複)を結合する時間を timeit で測りました。数値は環境で変わるので、桁の違いを見る目安として読んでください。

import timeit
from collections import ChainMap

A = {f"k{i}": i for i in range(1000)}
B = {f"k{i}": i for i in range(500, 1500)}

cases = [
    ("a | b", "A | B"),
    ("{**a, **b}", "{**A, **B}"),
    ("copy + update", "d = A.copy(); d.update(B)"),
    ("dict(ChainMap(b, a))", "dict(ChainMap(B, A))"),
    ("ChainMap(b, a) の作成のみ", "ChainMap(B, A)"),
]
for label, stmt in cases:
    sec = min(timeit.repeat(stmt, globals=globals(), number=2000, repeat=5)) / 2000
    print(f"{label:26s} {sec * 1e6:8.2f} µs")
書き方 Python 3.11.15 Python 3.14.7
a | b 25.90 µs 25.75 µs
{**a, **b} 26.90 µs 26.43 µs
copy + update 26.87 µs 25.08 µs
dict(ChainMap(b, a)) 338.06 µs 295.49 µs
ChainMap(b, a) の作成のみ 0.13 µs 0.15 µs
  • |、アンパック、copy + update の3つはほぼ同じ速さでした。読みやすさで選んで差し支えありません。
  • ChainMap は 作るだけなら一瞬(コピーしないため)ですが、dict() で普通の辞書に変換すると、今回の計測では約11〜13倍の時間がかかりました。
  • 「重ねて少しだけ参照する」なら ChainMap、「結合した辞書を何度も使う」なら a | b と使い分けると効率的です。

どの方法を選ぶかの判断表

flowchart TD
    A[辞書を結合したい] --> B{元の辞書を書き換えてよい?}
    B -- はい --> C{既にあるキーを上書きする?}
    C -- はい --> D["a.update(b) / a |= b"]
    C -- いいえ --> E["setdefault のループ"]
    B -- いいえ --> F{内側の辞書も混ぜたい?}
    F -- はい --> G["deep_merge(a, b)"]
    F -- いいえ --> H{コピーせずに参照だけしたい?}
    H -- はい --> I["ChainMap(b, a)"]
    H -- いいえ --> J["a | b(3.8以前は {**a, **b})"]
条件 おすすめ 理由
Python 3.9 以降で、新しい辞書がほしい a | b 短く、意図が伝わりやすい
3.8 以前でも動かす {**a, **b} 3.5 以降で同じ結果になる
結合しながらキーを足す {**a, **b, "key": value} 1つの式で書ける
a をその場で更新する a.update(b) どの版でも動き、ペアの並びやキーワード引数も渡せる
既にある値を残す setdefault のループ、または defaults | user 上書きを避けられる
設定ファイルのようなネストした辞書 deep_merge 内側の辞書を残したまま一部だけ変えられる
リストの値を連結する setdefault(k, []).extend(v) 置き換えではなく足し合わせになる
優先順位つきの設定を重ねる ChainMap コピーせず、元の変更が反映される

結合前のチェックリスト

確認すること 見るポイント
元の辞書を変えてよいか 変えたくなければ a | b か {**a, **b}
重複キーはどちらを残すか 残したい側を右(後ろ)に置く。ChainMap は先頭
値に辞書やリストが入っているか 入っていれば浅いマージで置き換わる。必要なら deep_merge
結合後に内側の値を書き換えるか 書き換えるなら copy.deepcopy で切り離す
動かす Python の版 3.8 以前なら | の代わりに {**a, **b}
None が渡ってくる可能性 b or {} で空の辞書に置き換える
update の戻り値を使っていないか 戻り値は None。結果は a 自体に入る

よくある質問

Q. update と | 演算子はどちらを使えばよいですか? A. 元の辞書をその場で更新したいなら a.update(b)、元の辞書を残して新しい辞書がほしいなら a | b です。重複キーの扱い(後から渡した側が残る)と速度は、今回の計測ではほぼ同じでした。Python 3.8 以前でも動かすコードでは、a | b の代わりに {**a, **b} を使います。

Q. 既にあるキーを上書きせずに結合するには? A. 優先したい辞書を右側に置いて defaults | user と書くか、for k, v in defaults.items(): user.setdefault(k, v) と書きます。前者は新しい辞書を作り、後者は user 自体に足りないキーだけを追加します。

Q. ネストした辞書の一部だけを更新したいのに、全体が置き換わります。 A. update、|、アンパックはいちばん外側のキーだけを見る浅いマージのため、値が辞書でも丸ごと置き換えます。本文の deep_merge のように、両方の値が辞書のときだけ再帰する関数を使うと、内側のキーを残したまま一部だけ更新できます。

Q. ChainMap と辞書の結合は何が違いますか? A. ChainMap は値をコピーせず、複数の辞書を順番に検索する入れ物です。元の辞書を変更すると参照結果も変わり、書き込みは先頭の辞書だけに届きます。優先されるのが「先に渡した辞書」である点も、右側優先の a | b と逆になります。

まとめ

  • 元の辞書を書き換えるのは update と |=、新しい辞書を作るのは | と {**a, **b} です。update の戻り値は None です。
  • キーが重複したときは、後から渡した側(右側)の値が残ります。 ChainMap と setdefault は、先にあった値が残ります。
  • | と |= は Python 3.9 以降で使えます。3.8.20 では TypeError になることを確認しました。3.8 以前では {**a, **b} を使います。
  • 標準の結合方法はすべて浅いマージで、ネストした辞書は丸ごと置き換わります。内側まで結合するには再帰マージ関数を使います。
  • 上書きしたくないときは setdefault、または優先したい辞書を右に置く defaults | user が使えます。
  • リストの値は setdefault(k, []).extend(v) で足し合わせ、数値の合計には Counter が使えます。
  • ChainMap はコピーせずに重ねて参照でき、作成は速い一方、辞書への変換には時間がかかります。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python辞書の結合・マージ updateと|演算子とsetdefault」

株式会社RJCでは、Pythonの辞書の結合やデータ構造の扱い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?