Python | データ構造強化:dict の更新

Python Python
スポンサーリンク

概要(dict の更新は「追加・上書き・統合」を最短で安全に行う技法)

辞書 dict はキーと値のペアを扱う可変データ構造です。更新は「代入で1件」「updateで複数件」「マージ演算子で統合」の3本柱が基本。特に、キー衝突時の“どちらを優先するか”、ネスト辞書での“浅い更新の限界”、そして“初期化しながら更新する setdefault”を押さえると、現場のデータ整形が短く、誤りなく書けます。

基本の使い方(ここが重要)

代入で追加・上書き(最短の1件更新)

キーが未登録なら追加、既存なら上書きになります。これが最短の更新手段です。

user = {"name": "alice", "age": 25}
user["city"] = "Tokyo"      # 追加
user["age"] = 26            # 上書き
Python

存在チェックは不要です。“キーがあるかどうかで動作が分かれる”のが辞書の自然な挙動です。

update で複数件をまとめて反映(戻り値は None に注意)

update は「辞書・キー値ペアのイテラブル・キーワード引数」から一括更新します。戻り値は None なので、チェーンせず“そのまま呼ぶ”のが正解です。

user = {"name": "alice"}

user.update({"age": 25, "city": "Tokyo"})          # 辞書から
user.update([("age", 26), ("role", "admin")])      # ペアの列から
user.update(country="JP", city="Kyoto")            # キーワード引数

# 悪い例: result = user.update({...})  # None が入ってしまう
Python

右側の値で上書きされる、という“優先順位”を頭に入れておくと安全です。

マージ演算子(| と |=)で「新辞書作成」と「破壊的統合」を選ぶ

Python 3.9+ では辞書の統合に | を使えます。新しい辞書を作るなら |、元を更新したいなら |= を選びます。

base = {"a": 1, "b": 2}
extra = {"b": 99, "c": 3}

merged = base | extra   # 新辞書(右が優先)→ {'a':1, 'b':99, 'c':3}
base |= extra           # 破壊的更新(右が優先)  base は上と同じ内容に
Python

重要ポイントの深掘り(衝突の優先順位・初期化更新・ネストの限界)

キー衝突の優先順位を明確化(右が勝つ)

update でも | でも、衝突時は“右側が勝つ”のが原則です。設定のオーバーライド順をコードで表現しやすく、読む側にも明快です。

defaults = {"timeout": 10, "retry": 2}
env      = {"retry": 5}
cli      = {"timeout": 20}

cfg = defaults | env | cli  # 最後に書いたものが最優先
Python

setdefault で「未登録なら初期化してから更新」する

存在しないキーに対して“初期値を入れてから加工”が一行で書けます。ネスト辞書やリスト追加の定番です。

stats = {}
stats.setdefault("coffee", {"sum": 0, "count": 0})
stats["coffee"]["sum"] += 120
stats["coffee"]["count"] += 2
Python

setdefault は「キーがなければその値を入れて返す/あれば既存値を返す」。存在チェックと初期化の if 文を消せます。

ネスト辞書の更新は「浅い更新」になる。必要なら段階的・再帰的に

update や | は浅い更新です。内側の辞書を“まるごと置き換える”ので、部分更新が必要なら段階的に書くか、再帰的更新関数を用意します。

cfg = {"db": {"host": "localhost", "port": 5432}, "debug": False}
patch = {"db": {"port": 5433}}           # host は残したい

# 浅い update だと db が丸ごと置き換わる → host が消える
tmp = cfg.copy()
tmp.update(patch)                         # {'db': {'port':5433}, 'debug': False}

# 段階的に書けば意図通り
cfg["db"].update(patch["db"])             # {'db': {'host':'localhost','port':5433}, 'debug': False}
Python

再帰的更新が頻繁なら、小さなユーティリティを書くのが現実的です。

def deep_update(a: dict, b: dict) -> dict:
    for k, v in b.items():
        if isinstance(v, dict) and isinstance(a.get(k), dict):
            deep_update(a[k], v)
        else:
            a[k] = v
    return a

cfg = {"db": {"host": "localhost", "port": 5432}, "debug": False}
patch = {"db": {"port": 5433}}
deep_update(cfg, patch)
Python

展開マージ({**a, **b})は「新辞書を作る」最短手段

一時的な結合で新辞書が欲しいなら、展開で軽く書けます。右が優先は同じです。

a = {"x": 1, "y": 2}
b = {"y": 9, "z": 3}
c = {**a, **b}   # {'x':1, 'y':9, 'z':3}
Python

実務での使いどころ(設定のオーバーライド・集計・正規化)

設定オーバーライドの順序をコードで表す

defaults → 環境変数 → CLI 引数の順で“右優先”統合すれば、期待通りの上書きになります。可視性が高く、保守も容易です。

cfg = defaults | env_cfg | cli_cfg
Python

集計と初期化を一行で(setdefault を活用)

ネスト辞書の集計では setdefault が冴えます。未登録時の初期化と更新をまとめて書けます。

sales = {}
def add_item(cat, name, qty):
    sales.setdefault(cat, {}).setdefault(name, 0)
    sales[cat][name] += qty
Python

API レスポンスを安全に正規化(update と初期値の併用)

必須フィールドが欠けていても安全に扱えるよう、初期値に update で上書きします。戻り値 None に注意して“直書き”します。

resp = {"id": 101, "name": "alice"}       # age が欠けている想定
normalized = {"id": None, "name": "", "age": 0}
normalized.update(resp)
Python

例題で身につける(定番から一歩先まで)

例題1:update の3通り入力と戻り値 None の注意

user = {"name": "alice"}
user.update({"age": 25})
user.update([("city", "Tokyo")])
user.update(role="admin")
# print(user.update({"age": 26}))  # None(使わない)
print(user)
Python

例題2:| と |= の違い(新辞書 vs 破壊的)

base = {"a": 1}
extra = {"a": 9, "b": 2}
merged = base | extra
print(merged)   # {'a': 9, 'b': 2}
base |= extra
print(base)     # {'a': 9, 'b': 2}
Python

例題3:setdefault でネストの初期化+更新

stats = {}
def add_score(user, score):
    stats.setdefault(user, {"sum": 0, "count": 0})
    stats[user]["sum"] += score
    stats[user]["count"] += 1

add_score("alice", 10)
add_score("alice", 5)
print(stats)
Python

例題4:ネスト辞書の部分更新(深い更新関数)

def deep_update(a, b):
    for k, v in b.items():
        if isinstance(v, dict) and isinstance(a.get(k), dict):
            deep_update(a[k], v)
        else:
            a[k] = v
    return a

cfg = {"svc": {"host": "localhost", "port": 8000}, "debug": False}
patch = {"svc": {"port": 8080}}
deep_update(cfg, patch)
print(cfg)  # host を保ったまま port だけ更新
Python

まとめ

dict の更新は「代入で1件」「updateで複数」「|・|=で統合」が基本線です。衝突時は“右が優先”であること、update の戻り値は None であること、浅い更新はネストを丸ごと置き換える点が最重要。未登録キーの初期化は setdefault が最短で、ネストの部分更新は段階的に書くか小さな deep_update を用意すると安全です。展開マージ {**a, **b} は新辞書作成の手早い選択肢。これらを使い分ければ、設定のオーバーライド、集計の初期化、API の正規化まで、短く明快で壊れないコードになります。

タイトルとURLをコピーしました