Pythonの辞書(dict):作成、メソッド、ループ、ソート、frozendict
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) を使います。ただし、キーは文字列として保存されます。