基礎

Pythonの辞書(dict):作成、メソッド、ループ、ソート、frozendict

更新日: · Skillaria.Top

Pythonの辞書(dict)は、「キーと値」のペアを保持する変更可能なコレクションです。キーを使うと値をほぼ瞬時に取り出せます。波かっこ {"コーヒー": 250} や dict() 関数で作成します。キーは一意で、ハッシュ可能でなければなりません。Python 3.7からは挿入順が保たれ、Python 3.15には変更できない組み込みの frozendict が加わりました。

Pythonの辞書(dict)は、「キーと値」のペアを保持するコレクションです。登録されている件数がどれだけ多くても、キーを使えば値をほぼ瞬時に取り出せます。カテゴリごとの支出を集計する、設定を保存する、ユーザーIDからユーザーを検索する、といった場面で辞書を使います。ここでは、辞書の作成、メソッド、ループ、ソート、結合、そしてPython 3.15の新機能 frozendict まで、辞書を扱うのに必要なことを一通り紹介します。

Pythonの辞書とは

辞書は、キーが一意になるように値を保持します。既存のキーに値を代入すると、古い値が置き換えられます。内部ではハッシュテーブルが使われているため、キーでの検索・追加・削除は、全体を順に調べるのではなく、平均して一定の時間で完了します。

キーにできるのは、ハッシュ可能なオブジェクトだけです。文字列、数値、変更できない値だけからなるタプルなどです。リストはキーにできません。その場合は TypeError: unhashable type: 'list' が出ます。一方、値には何でも使え、別の辞書やリストも入れられます。

Python 3.7以降では、辞書が挿入順を保証します。ループで回すと、キーは追加された順に現れます。

辞書の作成方法

辞書を作る方法はいくつかあり、どれも実際のコードで使われています。

spent = {"食費": 1200, "タクシー": 450}   # リテラル
empty = {}                               # 空の辞書
also_empty = dict()                      # dict 関数でも同じ
user = dict(name="花子", age=30)         # キーワード引数
pairs = dict([("a", 1), ("b", 2)])       # ペアのリストから
keys = dict.fromkeys(["x", "y"], 0)      # {'x': 0, 'y': 0}

注意:空の波かっこ {} は空の辞書を作ります。空の集合ではありません。空の集合は set() でのみ作れます。

2つのリストから辞書を作るには、zip が便利です。

names = ["食費", "タクシー", "映画"]
sums = [1200, 450, 600]
spent = dict(zip(names, sums))
print(spent)   # {'食費': 1200, 'タクシー': 450, '映画': 600}

キーで値を取り出す:d[k] と get

値は角かっこで読み出します。キーが存在しない場合、Pythonは KeyError を発生させます。エラーを避けたいときは get メソッドを使います。キーがなければ None か、指定したデフォルト値を返します。

spent = {"食費": 1200}
print(spent["食費"])            # 1200
print(spent.get("映画"))        # None
print(spent.get("映画", 0))     # 0
spent["映画"]                   # KeyError: '映画'

キーが存在するかどうかは、in 演算子で確認できます。例:"食費" in spent は True を返します。in が確認するのはキーであり、値ではありません。値を確認するには 1200 in spent.values() と書きます。

追加、変更、削除

項目の追加や更新は、代入1行で済みます。件数を数えるときは、get と組み合わせると便利です。

spent = {}
for category, amount in [("食費", 250), ("タクシー", 300), ("食費", 400)]:
    spent[category] = spent.get(category, 0) + amount
print(spent)   # {'食費': 650, 'タクシー': 300}

キーを削除するには、del d[k] を使います。キーがないと KeyError になります。または、d.pop(k, default) を使うと、削除と同時に値を取得できます。辞書を空にするには d.clear()、要素数(ペアの数)を調べるには len(d) を使います。

辞書のメソッド一覧

メソッド役割
get(k, default)キーの値を返す。なければ default を返し、エラーにはならない
setdefault(k, default)値を返す。キーがなければ、先に default を書き込んでから返す
update(other)別の辞書のペアを追加し、既存のキーは上書きする
pop(k, default)キーを削除して値を返す
popitem()最後に追加されたペアを削除して返す
keys()、values()、items()キー、値、ペアのビュー
copy()浅いコピーを作る
clear()辞書を空にする
fromkeys(keys, value)同じ値を持つ新しい辞書を作る

setdefault:値をリストで埋める

値がリストで、要素をグループ化したいときは、setdefault が役立ちます。

groups = {}
for item, cat in [("コーヒー", "食費"), ("電車", "交通費"), ("ピザ", "食費")]:
    groups.setdefault(cat, []).append(item)
print(groups)   # {'食費': ['コーヒー', 'ピザ'], '交通費': ['電車']}

update:辞書を更新する

update は辞書をその場で変更します。新しいキーは追加され、既存のキーの値は上書きされます。

cfg = {"debug": False, "lang": "ja"}
cfg.update({"debug": True, "theme": "dark"})
print(cfg)   # {'debug': True, 'lang': 'ja', 'theme': 'dark'}

defaultdict:初期値を持つ辞書

collections.defaultdict は dict のサブクラスで、存在しないキーにアクセスすると、ファクトリ関数を使って値を自動的に作成します。集計やグループ化のコードが短くなります。

from collections import defaultdict

spent = defaultdict(int)
for cat, amount in [("食費", 250), ("タクシー", 300), ("食費", 400)]:
    spent[cat] += amount
print(dict(spent))   # {'食費': 650, 'タクシー': 300}

groups = defaultdict(list)
groups["食費"].append("コーヒー")

単純に個数を数えるだけなら、collections.Counter もあります。例:Counter(["a", "b", "a"]) は Counter({'a': 2, 'b': 1}) を返します。

辞書のループ:for文

for で辞書を回すと、デフォルトではキーが順に取り出されます。キーと値の両方が必要なら、items() をループするのが最も一般的です。

spent = {"食費": 650, "タクシー": 300}

for cat in spent:                    # キー
    print(cat)

for amount in spent.values():        # 値
    print(amount)

for cat, amount in spent.items():    # ペア
    print(f"{cat}: {amount}円")

ループの途中で辞書の要素数を変えることはできません。RuntimeError: dictionary changed size during iteration が発生します。一部のキーを削除したい場合は、コピーをループしてください。例:for k in list(d):。

辞書を画面に表示する

print(d) は辞書を1行で表示します。大きなデータや入れ子のデータには、pprint モジュールか、json.dumps(d, ensure_ascii=False, indent=2) が便利です。インデント付きで、日本語もエスケープされずに表示されます。

辞書の並べ替え

辞書そのものはその場で並べ替えられません。sort メソッドはないためです。辞書を並べ替えるとは、sorted で並べ替えたペアから新しい辞書を作ることです。

spent = {"タクシー": 300, "食費": 650, "映画": 120}

by_key = dict(sorted(spent.items()))
print(by_key)    # {'映画': 120, '食費': 650, 'タクシー': 300}

by_value = dict(sorted(spent.items(), key=lambda p: p[1], reverse=True))
print(by_value)  # {'食費': 650, 'タクシー': 300, '映画': 120}

上位N件のカテゴリだけが必要なら、結果を辞書に戻す必要はありません。sorted(spent.items(), key=lambda p: p[1], reverse=True)[:2] は、ペアのリストを返します。

辞書のリストを、ある項目で並べ替えることもできます。例:sorted(users, key=lambda u: u["age"])。

辞書の結合

Python 3.9以降では、辞書を | 演算子で結合でき、|= を使うとその場で更新できます。キーが重なった場合は、右側の値が優先されます。

a = {"x": 1, "y": 2}
b = {"y": 20, "z": 3}
print(a | b)          # {'x': 1, 'y': 20, 'z': 3}
print({**a, **b})     # アンパックを使った同じ結果
a |= b                # a.update(b) と同じ

注意:辞書同士を a + b で足すことはできず、TypeError になります。同じキーの値を足し合わせたい場合は、Counter を使います。例:Counter(a) + Counter(b)。

辞書のアンパック

2つのアスタリスク ** は、辞書を関数の名前付き引数や別の辞書に展開します。

def greet(name, city):
    return f"{name}さん({city})"

data = {"name": "太郎", "city": "大阪"}
print(greet(**data))   # 太郎さん(大阪)

辞書内包表記

辞書内包表記は、任意の並びから1行で辞書を作ります。

squares = {n: n * n for n in range(5)}
print(squares)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

big = {k: v for k, v in spent.items() if v > 200}

inverted = {v: k for k, v in {"a": 1, "b": 2}.items()}
print(inverted)  # {1: 'a', 2: 'b'}

最後の例は、キーと値を入れ替えて辞書を反転させる方法です。値が一意でハッシュ可能なときにしか使えません。そうでないと、一部のペアが失われます。

入れ子の辞書

値として別の辞書を入れることもできます。APIのJSON応答や、ユーザーごとの辞書のような構造を表すときに使います。

users = {
    101: {"name": "花子", "skills": ["python", "sql"]},
    102: {"name": "健太", "skills": ["go"]},
}
print(users[101]["skills"][0])            # python
print(users.get(103, {}).get("name"))     # None、KeyError は発生しない

辞書のコピー:コピーと別名

b = a は辞書をコピーせず、同じオブジェクトに2つ目の名前を付けるだけです。b を変更すると、a にも変更が反映されます。辞書を関数に渡したときも同じことが起きます。関数は共有されたオブジェクトを受け取るため、気づかないうちに中身が書き換えられることがあります。

import copy

a = {"limits": [0, 500]}
b = a.copy()              # 浅いコピー
b["limits"].append(1000)  # 入れ子のリストは共有されている!
print(a)                  # {'limits': [0, 500, 1000]}

c = copy.deepcopy(a)      # 完全に独立したコピー

辞書の比較

== は辞書の内容を比較します。すべてのペアが一致すれば等しくなります。キーの順序は考慮されません。例:{"a": 1, "b": 2} == {"b": 2, "a": 1} は True です。共通のキーを探すには、ビューに対する集合演算を使います。例:a.keys() & b.keys()。

型付きの辞書:型注釈

辞書の型注釈は dict[str, int] のように書きます。フィールドごとに異なる型を持つ固定の構造なら、typing モジュールの TypedDict が便利です。

from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

u: User = {"name": "花子", "age": 30}

型注釈は実行時にはチェックされません。IDEやmypyのような型チェッカーが読み取ります。

変更できない辞書:Python 3.15のfrozendict

Python 3.15 では、frozendict という組み込みの型が追加されました(PEP 814)。インポートは不要で、変更しようとすると即座に TypeError になります。以前は、types モジュールのラッパーや外部パッケージで変更不可能な辞書を作る必要がありました。誰かが黙って書き換えてはいけない共通設定には、まさにうってつけです。

config = frozendict(min_amount=0, max_amount=500)
config["max_amount"] = 1000
# TypeError: 'frozendict' object does not support item assignment

strict = config | {"max_amount": 300}   # 新しい frozendict、元の値は変わらない
cache = {config: "5月のレポート"}        # ハッシュ可能なのでキーに使える

講座の資料で押さえておきたい点は次のとおりです。

  • frozendict は dict ではなく object を継承しています。そのため isinstance(cfg, dict) は False を返します。「任意の辞書」かどうかを判定するには、collections.abc.Mapping、または isinstance(x, (dict, frozendict)) を使います。
  • ハッシュ可能なのは、キーと値がすべてハッシュ可能な場合だけです。frozendict(a=[1]) は作成できますが、hash() は失敗します。不変性は浅いもので、タプルと同じです。
  • 順序は保持されますが、== の比較では考慮されません。
  • json.dumps、pickle、copy は frozendict に対応しています。

よくあるミス

  • 読み取り時の KeyError:get を使うか、in で事前に確認しましょう。
  • キーにリストを使う:タプルに変換します。例:d[tuple(lst)]。
  • ループ中に辞書を変更する:list(d) をループしましょう。
  • 代入でコピーができると思い込む:copy() か deepcopy を使います。
  • {} を空の集合だと思う:これは空の辞書です。

まとめ

Pythonの辞書は、言語の中で最も重要なデータ構造の1つです。キーによる高速な検索、挿入順の保持、get、setdefault、update といった便利なメソッド、items() によるループ、sorted による並べ替え、そして | による結合ができます。コピーの扱いとハッシュ可能なキーに注意し、Python 3.15 では設定に frozendict を使いましょう。辞書、リスト、タプル、集合は、講座「Python 3.15:初心者からプロへ」の家計簿ツールの例を使った実践的な講義で扱っています。

よくある質問

辞書の値には異なる型を混在させてもよいですか?

はい。値は数値、文字列、リスト、別の辞書など、何でも同じ辞書に入れられます。制限はキーだけにあり、キーはハッシュ可能でなければなりません。

辞書に入っている要素の数はどうやって調べますか?

len(d) でキーと値のペアの数を返します。空の辞書は 0 になり、if の条件では偽として扱われます。

dict と defaultdict の違いは何ですか?

defaultdict は dict のサブクラスで、存在しないキーにアクセスしても KeyError を出さず、int や list などのファクトリで値を作ります。それ以外の動作は通常の辞書と同じです。

値からキーを探すにはどうしますか?

直接のメソッドはないので、ループで探します。例:next((k for k, v in d.items() if v == value), None)。頻繁に行うなら、{v: k for k, v in d.items()} で逆向きの辞書を作る方が簡単です。

辞書をファイルに保存するにはどうしますか?

多くの場合 json モジュールを使います。書き込み用に開いたファイルに json.dump(d, f, ensure_ascii=False, indent=2) を書き、読み込みには json.load(f) を使います。ただし、キーは文字列として保存されます。

動画でこのテーマを学ぶ

このテーマはコース「Python 3.15: от новичка до профи」で実践とともに詳しく解説しています。

コースへ