16. 辞書: キーから値へのマッピング
前の章では、リスト(list)やタプル(tuple)について学びました。これらは、項目を特定の順序で保存し、位置によってアクセスできるコレクションです。しかし、番号よりも意味のあるものを使って情報を検索したい場合はどうでしょうか?学生の成績を名前で探したい、商品の価格を ID で探したい、単語の定義を単語そのもので探したい、といった場合です。
そこで登場するのが 辞書(dictionary) です。辞書は、キーと値のペア(key-value pairs) を保存するための Python 組み込みデータ構造です。位置(grades[0] のように)で項目にアクセスする代わりに、キー(key)(grades["Alice"] のように)でアクセスします。これにより、辞書は実世界のプログラムでデータを整理して取り出すために非常に強力な存在になります。
辞書は、現実の辞書や電話帳のようなものだと考えてください。単語(キー)を引いて定義(値)を見つけたり、名前を引いて電話番号を見つけたりします。Python の辞書も同じように、キーを値に対応付けることで、高速な検索と柔軟なデータ整理を可能にします。
16.1) 辞書の作成と値へのアクセス
16.1.1) 辞書とは?
辞書(dictionary) は キーと値のペア(key-value pairs) のコレクションです。各キーは値に関連付けられており、キーを使って値を取得します。キーは辞書内で一意である必要があり、同じキーのエントリを 2 つ持つことはできません。一方で、値は重複しても構いません。
基本構造は次のとおりです:
- キー(Keys): 値を検索するために使う一意の識別子(名前、ID、ラベルなど)
- 値(Values): 各キーに関連付けられたデータ(成績、価格、説明など)
辞書の特徴:
- 可変(mutable): 作成後にキーと値のペアを追加・変更・削除できます
- 順序なし(unordered)(Python 3.6 以前)または 挿入順(insertion-ordered)(Python 3.7+): 現代の Python では追加した順序が保持されますが、辞書は位置ではなくキーで項目にアクセスするコレクションだと考えるべきです
- 動的(dynamic): 必要に応じて大きくなったり小さくなったりします
16.1.2) 空の辞書とシンプルな辞書の作成
辞書を作る最も簡単な方法は、波かっこ {} を使い、コロンで区切ったキーと値のペアを並べることです:
# 空の辞書
empty_dict = {}
print(empty_dict) # Output: {}
print(type(empty_dict)) # Output: <class 'dict'>
# 学生の成績の辞書
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# 商品価格の辞書
prices = {"apple": 0.50, "banana": 0.30, "orange": 0.75}
print(prices) # Output: {'apple': 0.5, 'banana': 0.3, 'orange': 0.75}構文に注目してください。各キーと値のペアは key: value と書き、ペア同士はカンマで区切ります。ここではキーが文字列("Alice"、"apple")で、値が数値ですが、キーと値はいずれもさまざまな型にできます。
dict() コンストラクタを使って辞書を作ることもできます:
# キーワード引数で dict() を使う
student = dict(name="Alice", age=20, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 20, 'major': 'Computer Science'}
# タプルのリストで dict() を使う
colors = dict([("red", "#FF0000"), ("green", "#00FF00"), ("blue", "#0000FF")])
print(colors) # Output: {'red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF'}dict() コンストラクタは、他のデータ構造から辞書を構築する場合や、Python の識別子をキー(引用符なし)として使いたい場合に便利です。
16.1.3) キーで値にアクセスする
辞書から値を取り出すには、キーを角かっこで指定します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# 個別の値にアクセス
alice_grade = grades["Alice"]
print(alice_grade) # Output: 95
bob_grade = grades["Bob"]
print(bob_grade) # Output: 87これは辞書の値にアクセスする最も直接的な方法です。しかし、存在しないキーにアクセスしようとすると、Python は KeyError を送出します:
grades = {"Alice": 95, "Bob": 87}
# WARNING: KeyError - for demonstration only
# print(grades["David"]) # PROBLEM: KeyError: 'David'このエラーは、"David" が辞書内のキーではないために発生します。次の小節で、これを安全に扱う方法を学びます。
16.1.4) get() による安全なアクセス
キーが存在しない可能性があるときに KeyError を避けるには、get() メソッドを使います。キーが見つからない場合、None(または指定したデフォルト値)を返します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# get() による安全なアクセス
alice_grade = grades.get("Alice")
print(alice_grade) # Output: 95
# キーが存在しない - None を返す
david_grade = grades.get("David")
print(david_grade) # Output: None
# デフォルト値を指定する
david_grade = grades.get("David", 0)
print(david_grade) # Output: 0
# 条件分岐で get() を使う
if grades.get("Eve") is None:
print("Eve is not in the grade book") # Output: Eve is not in the grade bookget() メソッドは、キーが存在するか確信できない場合に、角かっこでの直接アクセスより安全です。get() の第 2 引数は、キーがないときに返すデフォルト値で、指定しない場合は None になります。
get() が役立つ場面を示す実用的な例です:
# 任意の情報を含む学生データベース
students = {
"Alice": {"age": 20, "major": "CS"},
"Bob": {"age": 19}, # Bob はまだ専攻を宣言していない
"Charlie": {"major": "Math"} # Charlie の年齢が記録されていない
}
# 欠けている可能性のある情報に安全にアクセスする
for name in ["Alice", "Bob", "Charlie"]:
student = students[name]
age = student.get("age", "Unknown")
major = student.get("major", "Undeclared")
print(f"{name}: Age {age}, Major {major}")
# Output:
# Alice: Age 20, Major CS
# Bob: Age 19, Major Undeclared
# Charlie: Age Unknown, Major Math16.1.5) 有効なキーの型
辞書のキーは ハッシュ可能(hashable) でなければなりません。これは専門用語で、キーの値が変化しないことを意味します。実際には次のことを指します:
有効なキーの型(イミュータブル):
- 文字列:
"name"、"id_123" - 数値:
42、3.14 - タプル(不変の要素のみを含む場合):
(1, 2)、("x", "y") - 真偽値:
True、False None
無効なキーの型(ミュータブル):
- リスト:
[1, 2, 3]はキーにできません - 辞書:
{"a": 1}はキーにできません - セット:
{1, 2, 3}はキーにできません
# 有効なキー
valid_dict = {
"name": "Alice", # 文字列キー
42: "answer", # 整数キー
3.14: "pi", # 浮動小数点キー
(1, 2): "coordinates", # タプルキー
True: "yes", # 真偽値キー
None: "nothing" # None キー
}
print(valid_dict["name"]) # Output: Alice
print(valid_dict[42]) # Output: answer
print(valid_dict[(1, 2)]) # Output: coordinates
# WARNING: 無効なキー - デモ用のみ
# invalid_dict = {[1, 2]: "list key"} # PROBLEM: TypeError: unhashable type: 'list'
# invalid_dict = {{1, 2}: "set key"} # PROBLEM: TypeError: unhashable type: 'set'よくある初心者のミス: よくあるエラーは、論理的に見えるという理由でリストを辞書のキーとして使おうとすることです。たとえば位置データを保存するために、座標 [x, y] をキーとして使いたいかもしれません。Python が TypeError: unhashable type: 'list' を送出すると、初心者はなぜなのか理解できないことが多いです。というのも、そのリストにはキーとして使いたいデータがまさに入っているからです。
理由は、リストがミュータブル(変更可能)であり、Python は辞書のキーに安定して変化しないものを必要とするためです。リストのようなものをキーとして使いたい場合は、まずタプルに変換してください。tuple([1, 2]) は (1, 2) になり、キーとして使えます。タプルはイミュータブルなので、問題なく動作します:
# Wrong: リストをキーにしようとする
# locations = {[0, 0]: "origin", [1, 0]: "east"} # PROBLEM: TypeError
# Right: タプルに変換する
locations = {(0, 0): "origin", (1, 0): "east", (0, 1): "north"}
print(locations[(0, 0)]) # Output: origin
print(locations[(1, 0)]) # Output: east一方で、値はミュータブルでもイミュータブルでも、どんな型でも構いません:
# 値はどんな型でもよい
flexible_dict = {
"numbers": [1, 2, 3], # リスト値
"nested": {"a": 1, "b": 2}, # 辞書値
"function": len, # 関数値
"mixed": (1, [2, 3], {"x": 4}) # ミュータブルな要素を含むタプル
}
print(flexible_dict["numbers"]) # Output: [1, 2, 3]
print(flexible_dict["nested"]["a"]) # Output: 1ハッシュ可能性については第 17 章でより深く扱いますが、今は次を覚えておいてください。キーには不変型(文字列、数値、タプル)を使えば大丈夫です。
16.2) 辞書エントリの追加と更新
16.2.1) 新しいキーと値のペアを追加する
辞書に新しいエントリを追加するのは簡単です。新しいキーに値を代入するだけです:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# 新しい学生を追加
grades["Charlie"] = 92
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# 別の学生を追加
grades["Diana"] = 88
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}キーが存在しなければ Python が作成します。存在する場合は値を更新します(次で扱います)。
16.2.2) 既存の値を更新する
値を更新するには、既存キーに新しい値を代入するだけです:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Bob の成績を更新
grades["Bob"] = 90
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92}
# 複数の成績を更新
grades["Alice"] = 98
grades["Charlie"] = 94
print(grades) # Output: {'Alice': 98, 'Bob': 90, 'Charlie': 94}Python は追加と更新を区別しません。構文は同じです。キーが存在すれば値が更新され、存在しなければ新しいエントリが作られます。
追加と更新の両方を示す実用的な例です:
# 在庫を追跡する
inventory = {"apple": 50, "banana": 30}
print("Initial inventory:", inventory) # Output: Initial inventory: {'apple': 50, 'banana': 30}
# りんごを補充する(既存を更新)
inventory["apple"] = inventory["apple"] + 20
print("After restocking apples:", inventory) # Output: After restocking apples: {'apple': 70, 'banana': 30}
# 新しい商品を追加(新規キーを追加)
inventory["orange"] = 40
print("After adding oranges:", inventory) # Output: After adding oranges: {'apple': 70, 'banana': 30, 'orange': 40}
# バナナをいくらか売る(既存を更新)
inventory["banana"] = inventory["banana"] - 10
print("After selling bananas:", inventory) # Output: After selling bananas: {'apple': 70, 'banana': 20, 'orange': 40}16.2.3) update() を使って辞書をマージする
update() メソッドは、複数のキーと値のペアをまとめて追加したり、別の辞書を現在の辞書にマージしたりします:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# 複数の学生を一度に追加
new_students = {"Charlie": 92, "Diana": 88}
grades.update(new_students)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# 既存の更新と新規の追加
more_updates = {"Bob": 90, "Eve": 85} # Bob の成績が変わり、Eve は新規
grades.update(more_updates)
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92, 'Diana': 88, 'Eve': 85}update() は辞書をインプレースで変更します。キーがすでに存在する場合は値が更新され、存在しない場合はキーと値のペアが追加されます。
update() にキーワード引数を渡すこともできます:
student = {"name": "Alice", "age": 20}
print(student) # Output: {'name': 'Alice', 'age': 20}
# キーワード引数で更新する
student.update(age=21, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}設定をマージする実用例です:
# デフォルト設定
config = {
"theme": "light",
"font_size": 12,
"auto_save": True
}
# ユーザー設定(デフォルトの一部を上書き)
user_prefs = {
"theme": "dark",
"font_size": 14
}
# ユーザー設定を config にマージする
config.update(user_prefs)
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}16.2.4) setdefault() を使ってキーが存在しないときだけ追加する
setdefault() メソッドは、キーがまだ存在しない場合にだけキーと値のペアを追加したいときに便利です。キーが存在する場合は、変更せずに現在の値を返します:
grades = {"Alice": 95, "Bob": 87}
# Charlie を追加(キーが存在しない)
result = grades.setdefault("Charlie", 90)
print(result) # Output: 90
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90}
# Alice を追加しようとする(キーが存在する - 変更なし)
result = grades.setdefault("Alice", 80)
print(result) # Output: 95 (existing value returned)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90} (unchanged)これは、必要な設定キーがすべてデフォルト値で存在することを保証しつつ、ユーザーがすでにカスタマイズした値は保持したい場合に特に便利です:
# デフォルトを含むアプリケーション設定
config = {"theme": "light", "font_size": 12}
# 必須の設定がすべてデフォルト値で存在することを保証する
config.setdefault("auto_save", True)
config.setdefault("language", "en")
config.setdefault("theme", "dark") # 変更されない - すでに存在する
print(config)
# Output: {'theme': 'light', 'font_size': 12, 'auto_save': True, 'language': 'en'}
# これで全設定に安全にアクセスできる
print(f"Theme: {config['theme']}") # Output: Theme: light
print(f"Auto-save: {config['auto_save']}") # Output: Auto-save: True
print(f"Language: {config['language']}") # Output: Language: en16.3) del と pop() で辞書エントリを削除する
16.3.1) del でエントリを削除する
del 文は、辞書からキーと値のペアを削除します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Charlie を削除
del grades["Charlie"]
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Diana': 88}
# 別の学生を削除
del grades["Bob"]
print(grades) # Output: {'Alice': 95, 'Diana': 88}存在しないキーを削除しようとすると、Python は KeyError を送出します:
grades = {"Alice": 95, "Bob": 87}
# WARNING: KeyError - for demonstration only
# del grades["Charlie"] # PROBLEM: KeyError: 'Charlie'存在しない可能性があるキーを安全に削除するには、先に確認します:
grades = {"Alice": 95, "Bob": 87}
# 安全な削除
if "Charlie" in grades:
del grades["Charlie"]
else:
print("Charlie not found") # Output: Charlie not found16.3.2) pop() で削除しつつ取得する
pop() メソッドはキーを削除し、その値を返します。エントリを削除しつつ値を使いたい場合に便利です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Bob を削除して成績を取得
bob_grade = grades.pop("Bob")
print(bob_grade) # Output: 87
print(grades) # Output: {'Alice': 95, 'Charlie': 92}
# 削除して値を利用する
charlie_grade = grades.pop("Charlie")
print(f"Charlie's final grade was {charlie_grade}") # Output: Charlie's final grade was 92
print(grades) # Output: {'Alice': 95}del と同様に、pop() もキーが存在しない場合は KeyError を送出します。ただし、代わりに返すデフォルト値を指定できます:
grades = {"Alice": 95, "Bob": 87}
# デフォルト値付き pop(キーが存在しない)
diana_grade = grades.pop("Diana", 0)
print(diana_grade) # Output: 0
print(grades) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
# デフォルト値付き pop(キーが存在する)
alice_grade = grades.pop("Alice", 0)
print(alice_grade) # Output: 95
print(grades) # Output: {'Bob': 87}16.3.3) clear() で全エントリを削除する
clear() メソッドは、辞書のすべてのキーと値のペアを削除し、空にします:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# 全エントリをクリア
grades.clear()
print(grades) # Output: {}
print(len(grades)) # Output: 0これは、空の辞書への再代入(grades = {})よりも明示的です。特に、他の変数が同じ辞書を参照している場合に違いが出ます:
# 違いを示す
grades = {"Alice": 95, "Bob": 87}
backup = grades # backup は同じ辞書を参照している
# clear() を使う - 両方の変数に影響する
grades.clear()
print(grades) # Output: {}
print(backup) # Output: {} (same dictionary was cleared)
# 次の例のためにリセット
grades = {"Alice": 95, "Bob": 87}
backup = grades
# 再代入 - grades にのみ影響する
grades = {}
print(grades) # Output: {}
print(backup) # Output: {'Alice': 95, 'Bob': 87} (different dictionary now)この挙動は、第 18 章で参照セマンティクス(reference semantics)を扱うときにさらに詳しく見ていきますが、今は次を覚えておいてください。clear() は既存の辞書を空にし、再代入は新しい空の辞書を作ります。
16.4) 辞書のビューオブジェクト: keys(), values(), items()
16.4.1) 辞書ビューの理解
辞書には、ビューオブジェクト(view objects) を返す 3 つのメソッドがあります。ビューオブジェクトは、辞書のキー、値、またはキーと値のペアを動的に参照するための特別なオブジェクトです。これらのビューは、辞書の変更を自動的に反映します:
keys(): すべてのキーのビューを返すvalues(): すべての値のビューを返すitems(): すべてのキーと値のペア(タプルとして)のビューを返す
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# ビューを取得
keys_view = grades.keys()
values_view = grades.values()
items_view = grades.items()
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
print(values_view) # Output: dict_values([95, 87, 92])
print(items_view) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])これらはリストではなくビューオブジェクトです。ただし、必要ならリストに変換できます:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# ビューをリストに変換
keys_list = list(grades.keys())
values_list = list(grades.values())
items_list = list(grades.items())
print(keys_list) # Output: ['Alice', 'Bob', 'Charlie']
print(values_list) # Output: [95, 87, 92]
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]16.4.2) ビューは動的である
ビューオブジェクトは、辞書の変更を自動的に反映します:
grades = {"Alice": 95, "Bob": 87}
# ビューを作成
keys_view = grades.keys()
print(keys_view) # Output: dict_keys(['Alice', 'Bob'])
# 辞書を変更
grades["Charlie"] = 92
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# エントリを削除
del grades["Bob"]
print(keys_view) # Output: dict_keys(['Alice', 'Charlie'])この動的な挙動は、変更される可能性のある辞書の内容を扱う必要があるときに便利です。しかし、変化しないスナップショットが必要なら、ビューをリストに変換してください:
grades = {"Alice": 95, "Bob": 87}
# スナップショットを作成
keys_snapshot = list(grades.keys())
print(keys_snapshot) # Output: ['Alice', 'Bob']
# 辞書を変更
grades["Charlie"] = 92
print(keys_snapshot) # Output: ['Alice', 'Bob'] (unchanged)16.4.3) keys() を使う
keys() メソッドは、辞書のすべてのキーのビューを返します。どんなキーが存在するかを確認したり、それらを反復処理したりするのに便利です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# すべてのキーを取得
keys = grades.keys()
print(keys) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# キーが存在するか確認
if "Alice" in keys:
print("Alice is in the grade book") # Output: Alice is in the grade book
# キー数を数える
print(f"Number of students: {len(keys)}") # Output: Number of students: 3keys() を呼ばずに、辞書そのものに対してメンバーシップを確認することもできます:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# これらは同じ
if "Alice" in grades.keys():
print("Found (using keys())")
if "Alice" in grades:
print("Found (direct check)") # こちらのほうが一般的で簡潔
# Output:
# Found (using keys())
# Found (direct check)16.4.4) values() を使う
values() メソッドは、辞書のすべての値のビューを返します。キーを気にせず値だけを処理したい場合に便利です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# すべての値を取得
values = grades.values()
print(values) # Output: dict_values([95, 87, 92, 88])
# 統計を計算
total = sum(values)
count = len(values)
average = total / count
print(f"Total points: {total}") # Output: Total points: 362
print(f"Number of students: {count}") # Output: Number of students: 4
print(f"Average grade: {average}") # Output: Average grade: 90.5
# 最高点と最低点を見つける
print(f"Highest grade: {max(values)}") # Output: Highest grade: 95
print(f"Lowest grade: {min(values)}") # Output: Lowest grade: 8716.4.5) items() を使う
items() メソッドは、キーと値のペアをタプルとして返すビューを返します。キーと値の両方が得られるため、これは最もよく使われるビューです:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# すべてのキーと値のペアを取得
items = grades.items()
print(items) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])
# タプルをはっきり見るためリストに変換
items_list = list(items)
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]
# 個別のタプルにアクセス
first_item = items_list[0]
print(first_item) # Output: ('Alice', 95)
print(first_item[0]) # Output: Alice
print(first_item[1]) # Output: 95items() のビューは反復処理に特に有用で、次のセクションで詳しく扱います。ここでは予告として示します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# 各キーと値のペアを処理
for name, grade in grades.items():
print(f"{name}: {grade}")
# Output:
# Alice: 95
# Bob: 87
# Charlie: 9216.5) キー・値・アイテムを反復処理する
16.5.1) キーを反復処理する(デフォルトの挙動)
辞書を for ループで直接反復処理すると、キーを反復処理します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# キーを反復処理(暗黙)
for name in grades:
print(name)
# Output:
# Alice
# Bob
# Charlieこれは grades.keys() を反復処理するのと同じです:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# キーを反復処理(明示)
for name in grades.keys():
print(name)
# Output:
# Alice
# Bob
# Charlieどちらも同じように動作します。暗黙のバージョン(.keys() なし)のほうが一般的で簡潔です。
キーを使って値にアクセスする実用例です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# 合格した学生を見つける(grade >= 90)
passing_students = []
for name in grades:
if grades[name] >= 90:
passing_students.append(name)
print("Students who passed:", passing_students) # Output: Students who passed: ['Alice', 'Charlie']16.5.2) 値を反復処理する
値だけを反復処理するには values() を使います:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# 値を反復処理
for grade in grades.values():
print(grade)
# Output:
# 95
# 87
# 92
# 88これは、値を処理したいが、それがどのキーに対応しているかは気にしない場合に便利です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# 合計と平均を計算
total = 0
count = 0
for grade in grades.values():
total = total + grade
count = count + 1
average = total / count
print(f"Class average: {average}") # Output: Class average: 90.5
# 全員が合格したか確認
all_passed = True
for grade in grades.values():
if grade < 60:
all_passed = False
break
if all_passed:
print("All students passed!") # Output: All students passed!16.5.3) items() でキーと値のペアを反復処理する
最も一般的で有用な反復処理パターンは、items() を使ってキーと値の両方を得る方法です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# キーと値のペアを反復処理
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Output:
# Alice scored 95
# Bob scored 87
# Charlie scored 92for name, grade in grades.items() のタプルのアンパックに注目してください。各アイテムは ("Alice", 95) のようなタプルで、それを 2 つの変数に展開しています。これはタプルの添字でアクセスするよりはるかに読みやすくなります:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# アンパックなし(読みづらい)
for item in grades.items():
print(f"{item[0]} scored {item[1]}")
# アンパックあり(読みやすい)
for name, grade in grades.items():
print(f"{name} scored {grade}")
# どちらも同じ出力:
# Alice scored 95
# Bob scored 87
# Charlie scored 9216.5.4) 反復処理中に辞書を変更する
警告: 反復処理中に辞書のサイズ(キーの追加や削除)を変更すると、エラーや予期しない挙動の原因になります:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# WARNING: RuntimeError - for demonstration only
# for name in grades:
# if grades[name] < 90:
# del grades[name] # PROBLEM: RuntimeError: dictionary changed size during iteration現代の Python(3.7+)では、辞書のサイズを変更しようとした瞬間に RuntimeError が直ちに発生します。Python は変更を検出し、予測不能な挙動を防ぐために実行を停止します。
古い Python バージョンでは、反復子が次のようなことを引き起こす可能性がありました:
- 処理すべき項目をスキップする
- 同じ項目を 2 回処理する
- 一貫しない結果を出す
そのため、Python は現在、分かりやすいエラーメッセージで即座に失敗するようになっています。
反復処理中に辞書を変更する必要がある場合は、キーのコピーを反復処理してください:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# 安全: キーのコピーを反復処理する
for name in list(grades.keys()):
if grades[name] < 90:
del grades[name]
print(grades) # Output: {'Alice': 95, 'Charlie': 92}または、必要なエントリだけを含む新しい辞書を作成します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# フィルタしたエントリだけで新しい辞書を作る
high_grades = {}
for name, grade in grades.items():
if grade >= 90:
high_grades[name] = grade
print(high_grades) # Output: {'Alice': 95, 'Charlie': 92}2 つ目の方法のほうが、より明確で安全なことが多いです。第 35 章では、辞書内包表記(dictionary comprehensions)を使って、さらにエレガントな方法を学びます。
16.6) よく使う辞書メソッド
16.6.1) in と not in でキーの存在を確認する
in と not in 演算子は、辞書にキーが存在するかどうかを確認します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# キーの存在を確認
if "Alice" in grades:
print("Alice is in the grade book") # Output: Alice is in the grade book
if "David" not in grades:
print("David is not in the grade book") # Output: David is not in the grade bookこれは、値にアクセスする前にキーが存在するかを確認するための推奨される方法です。get() を使って None をチェックするより、読みやすく Pythonic です:
grades = {"Alice": 95, "Bob": 87}
# 推奨: in を使う
if "Alice" in grades:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 95
# 別案: get() を使って None をチェック
if grades.get("Alice") is not None:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 9516.6.2) len() でエントリ数を取得する
len() 関数は、辞書のキーと値のペアの数を返します:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(len(grades)) # Output: 3
# 空の辞書
empty = {}
print(len(empty)) # Output: 0
# 変更後
grades["Diana"] = 88
print(len(grades)) # Output: 4
del grades["Bob"]
print(len(grades)) # Output: 3これは、辞書が空かどうかを確認したり、統計を報告したりするのに便利です:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
if len(grades) == 0:
print("No students in grade book")
else:
total = sum(grades.values())
average = total / len(grades)
print(f"{len(grades)} students, average grade: {average}")
# Output: 4 students, average grade: 90.516.6.3) copy() で辞書をコピーする
copy() メソッドは、辞書の 浅いコピー(shallow copy) を作成します。これは、同じキーと値のペアを持つ新しい辞書です:
original = {"Alice": 95, "Bob": 87}
duplicate = original.copy()
print(original) # Output: {'Alice': 95, 'Bob': 87}
print(duplicate) # Output: {'Alice': 95, 'Bob': 87}
# コピーを変更
duplicate["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
print(duplicate) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}これは単純な代入とは異なり、代入は同じ辞書への参照を作ります:
original = {"Alice": 95, "Bob": 87}
reference = original # コピーではない - 同じ辞書
# 参照を通して変更
reference["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92} (changed!)
print(reference) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}浅いコピーと深いコピーについては第 18 章で詳しく扱います。今は次を覚えておいてください。辞書の独立した複製が欲しい場合は copy() を使います。
16.6.4) | 演算子で辞書をマージする(Python 3.9+)
Python 3.9 では、辞書をマージするための | 演算子が導入されました。| 演算子は、両方の辞書からすべてのキーを結合した新しい辞書を作成します。重複するキーについては、右側の値が左側の値を上書きします。元の 2 つの辞書はどちらも変更されません。
defaults = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# 辞書をマージ(user_prefs が defaults を上書き)
config = defaults | user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}
# 元の辞書は変更されない
print(defaults) # Output: {'theme': 'light', 'font_size': 12, 'auto_save': True}
print(user_prefs) # Output: {'theme': 'dark', 'font_size': 14}複数のソースからのデータを結合するのに役立つ例をもう 1 つ示します:
# 2 つのサプライヤーからの製品情報
supplier_a = {
"laptop": {"price": 999.99, "stock": 15},
"mouse": {"price": 29.99, "stock": 50}
}
supplier_b = {
"laptop": {"price": 949.99, "stock": 20}, # より良い価格と在庫
"keyboard": {"price": 79.99, "stock": 30}
}
# マージ: 一致する商品について supplier_b のデータが supplier_a を上書きする
combined = supplier_a | supplier_b
print(combined)
# Output: {'laptop': {'price': 949.99, 'stock': 20}, 'mouse': {'price': 29.99, 'stock': 50}, 'keyboard': {'price': 79.99, 'stock': 30}}
# これで laptop は supplier_b(より良い条件)、mouse は supplier_a、keyboard は supplier_b のものになる|= 演算子はインプレースでマージします(左側の辞書を変更します):
config = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# インプレースでマージ
config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}これは update() を使うのと同等で、より簡潔です:
config = {"theme": "light", "font_size": 12}
user_prefs = {"theme": "dark", "font_size": 14}
# これらは同等
config.update(user_prefs)
# config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14}16.7) 構造化データのためのネストした辞書
16.7.1) ネストした辞書を作る
なぜネストした辞書を使うのか? 学生情報を追跡するとします。ages = {"Alice": 20, "Bob": 19}、majors = {"Alice": "CS", "Bob": "Math"}、gpas = {"Alice": 3.8, "Bob": 3.6} のように別々の辞書を作ることもできます。しかし、これは扱いにくくなります。3 つの辞書を同期させ続ける必要があり、ある学生の全情報を調べるには 3 回別々に検索しなければなりません。ネストした辞書は、関連するデータをまとめることでこれを解決します。学生名 1 つをキーにすると、その学生の情報すべてを 1 回の検索で取得できます。
ネストした辞書(nested dictionary) とは、値として別の辞書を含む辞書のことです。これは構造化された階層的データを表現するのに便利です:
# 複数属性を持つ学生レコード
students = {
"Alice": {
"age": 20,
"major": "Computer Science",
"gpa": 3.8
},
"Bob": {
"age": 19,
"major": "Mathematics",
"gpa": 3.6
},
"Charlie": {
"age": 21,
"major": "Physics",
"gpa": 3.9
}
}
print(students)
# Output: {'Alice': {'age': 20, 'major': 'Computer Science', 'gpa': 3.8}, 'Bob': {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}, 'Charlie': {'age': 21, 'major': 'Physics', 'gpa': 3.9}}各学生名は、その属性を含む別の辞書に対応付けられています。この構造は、属性ごとに別々の辞書を使うよりも、はるかに柔軟で保守しやすいものです。
16.7.2) ネストした値にアクセスする
ネストした値にアクセスするには、角かっこを複数回使います:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# ネストした値にアクセス
alice_age = students["Alice"]["age"]
print(alice_age) # Output: 20
bob_major = students["Bob"]["major"]
print(bob_major) # Output: Mathematics
# 式の中で使う
print(f"Alice is {students['Alice']['age']} years old")
# Output: Alice is 20 years old
print(f"Bob's GPA: {students['Bob']['gpa']}")
# Output: Bob's GPA: 3.6各角かっこがネストの 1 段階にアクセスします。まず students["Alice"] で内側の辞書を取り出し、次に ["age"] でその辞書から年齢の値を取り出します。
16.7.3) ネストした値を安全に取得する
ネストした辞書にアクセスする場合、各レベルが存在することを確認する必要があります:
students = {
"Alice": {"age": 20, "major": "Computer Science"},
"Bob": {"age": 19} # Bob は専攻を宣言していない
}
# 安全ではないアクセス - KeyError を起こす可能性がある
# print(students["Bob"]["major"]) # PROBLEM: KeyError: 'major'
# 複数チェックで安全にアクセス
if "Bob" in students:
if "major" in students["Bob"]:
print(f"Bob's major: {students['Bob']['major']}")
else:
print("Bob hasn't declared a major") # Output: Bob hasn't declared a major
# get() を使った安全なネストアクセス
bob_major = students.get("Bob", {}).get("major", "Undeclared")
print(f"Bob's major: {bob_major}") # Output: Bob's major: Undeclaredget() の連鎖が機能するのは、"Bob" が存在しない場合に最初の get() が空の辞書 {} を返し、その後の 2 回目の get() が安全に "Undeclared" を返すためです。
16.7.4) ネストした辞書を変更する
ネストした辞書のエントリを追加・更新・削除できます:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# ネストした値を更新
students["Alice"]["gpa"] = 3.9
print(f"Alice's new GPA: {students['Alice']['gpa']}") # Output: Alice's new GPA: 3.9
# 既存学生に新しい属性を追加
students["Bob"]["email"] = "bob@university.edu"
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6, 'email': 'bob@university.edu'}
# ネストしたデータを持つ新しい学生を追加
students["Charlie"] = {
"age": 21,
"major": "Physics",
"gpa": 3.7
}
print(f"Number of students: {len(students)}") # Output: Number of students: 3
# 属性を削除
del students["Bob"]["email"]
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}16.7.5) ネストした辞書を反復処理する
ネストした辞書は、ネストしたループで反復処理できます:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9}
}
# 学生と属性を反復処理
for name, info in students.items():
print(f"\n{name}:")
for key, value in info.items():
print(f" {key}: {value}")
# Output:
# Alice:
# age: 20
# major: Computer Science
# gpa: 3.8
#
# Bob:
# age: 19
# major: Mathematics
# gpa: 3.6
#
# Charlie:
# age: 21
# major: Physics
# gpa: 3.9特定の条件を満たす学生を見つける実用例です:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9},
"Diana": {"age": 20, "major": "Computer Science", "gpa": 3.5}
}
# GPA が 3.7 以上の CS 学生を見つける
print("High-performing CS students:")
for name, info in students.items():
if info["major"] == "Computer Science" and info["gpa"] >= 3.7:
print(f" {name} (GPA: {info['gpa']})")
# Output:
# High-performing CS students:
# Alice (GPA: 3.8)辞書は、Python で最も強力で汎用性の高いデータ構造の 1 つです。高速な検索、柔軟な整理、そしてよくあるプログラミング課題に対するエレガントな解決策を提供します。Python 学習を続けていくと、設定、データ処理、複雑なアプリケーション構築など、あらゆる場面で辞書が登場することに気づくでしょう。
この章で扱ったパターン(カウント、グループ化、ルックアップテーブル、データ変換)は、Python で構造化データを扱うための基盤を形成します。次の章では、辞書を補完し、重複のない順序なしデータを扱うための別のコレクション型であるセット(set)を学びます。