エラーの内容
プログラムが実行中に、一見シンプルに見える一行でクラッシュします:
KeyError: 'key_name'
完全なトレースバック:
Traceback (most recent call last):
File "app.py", line 5, in <module>
value = data['username']
KeyError: 'username'
つまり、辞書に存在しないキーをPythonに要求したということです。Pythonは推測しません — ただクラッシュします。
なぜこのエラーが発生するのか
ブラケット記法(dict['key'])は厳格です。キーが存在しない場合、Pythonは即座にKeyErrorを発生させます — フォールバックも警告もありません。よくある原因:
- キー名のタイポ —
'Username'vs'username'(Pythonは大文字小文字を区別します) - キーが辞書に追加されていなかった
- そのフィールドが存在しない場合があるAPIまたはJSONレスポンス
- コードの前の部分でキーが削除された
- すべてのアイテムが同じキーを持つわけではない混合構造のデータをイテレートしている
ステップごとの修正方法
ステップ1 — まず辞書を出力する
推測しないでください。辞書を出力して、中身を正確に確認しましょう:
data = {'user': 'alice', 'age': 30}
print(data.keys()) # dict_keys(['user', 'age'])
print(data) # {'user': 'alice', 'age': 30}
十中八九、これで原因がすぐにわかります — 'username' vs 'user'、または予期しない大文字小文字の違いなど。
ステップ2 — 安全なアクセスのために.get()に切り替える
.get(key, default)は最もシンプルな修正方法です。クラッシュする代わりにNone(またはデフォルト値)を返します:
# 修正前 — キーが存在しない場合クラッシュする
value = data['username']
# 修正後 — 安全
value = data.get('username') # キーが存在しない場合はNoneを返す
value = data.get('username', 'guest') # キーが存在しない場合は'guest'を返す
この一つの変更で、APIレスポンス処理における大半のKeyErrorを排除できます。
ステップ3 — アクセス前にinで確認する
キーの存在有無によって異なるロジックが必要な場合、in演算子がすっきりと対応します:
if 'username' in data:
value = data['username']
print(f"こんにちは、{value}")
else:
print("データにusernameが見つかりません")
ステップ4 — setdefault()で読み取りとフォールバックの保存を一度に行う
キーを読み取りながら、存在しない場合にデフォルト値を保存したい場合に便利です:
data = {}
value = data.setdefault('count', 0) # 'count': 0 を追加して0を返す
print(data) # {'count': 0}
ステップ5 — キーが多く欠けている辞書にはdefaultdictを使う
集計やカウンターを作成していますか?collections.defaultdictは最初のアクセス時に自動的に欠けているキーを作成するため、エラーは発生しません:
from collections import defaultdict
counts = defaultdict(int) # 欠けているキーはデフォルトで0
words = ['apple', 'banana', 'apple', 'cherry']
for word in words:
counts[word] += 1 # KeyErrorなし、自動的に0から開始
print(dict(counts)) # {'apple': 2, 'banana': 1, 'cherry': 1}
ステップ6 — ネストした辞書には連鎖した.get()が必要
JSONレスポンスで3段階の深さがありますか?一つのレベルが欠けると、チェーン全体でKeyErrorが発生します。空の辞書をフォールバックとして.get()呼び出しを連鎖させることで修正できます:
response = {
'user': {
'profile': {
'email': 'alice@example.com'
}
}
}
# 危険 — どのレベルが欠けてもKeyErrorが発生する
email = response['user']['profile']['email']
# 安全 — どのレベルが存在しなくてもNoneを返す
email = response.get('user', {}).get('profile', {}).get('email')
print(email) # 'alice@example.com' または None
修正の確認
コードを再実行してください。トレースバックがなければ修正は成功です。以下の簡単なサニティチェックで3つのシナリオを全て確認できます:
data = {'name': 'Bob'}
print(data.get('name')) # Bob
print(data.get('email', '未設定')) # 未設定
print(data.get('age')) # None (クラッシュなし)
クイックリファレンス
dict['key']— キーが存在しない場合クラッシュする(キーの存在が保証されている場合のみ使用)dict.get('key')— 安全にNoneを返すdict.get('key', default)— フォールバック値を返す'key' in dict— アクセス前に存在を確認するdict.setdefault('key', default)— 読み取りとデフォルト挿入を一度に行うdefaultdict(type)— アクセス時に欠けているキーを自動作成する
ヒント
- APIレスポンスとJSONデータは予測不能です — 常に
.get()を使用してください。99%のレスポンスに存在するフィールドも、残りの1%では欠けている可能性があります。 - ループ内での
KeyErrorは、通常、あるアイテムの構造が異なることを意味します。各アイテムを出力して、異なるものを見つけましょう。 - 欠けているキーが本当のバグであるコンフィグ辞書の場合は、
dict['key']のままにしてください。どこでも.get()を使うと、本物の問題を静かに飲み込んでしまいます。 - Python 3.8以降では、
TypedDictやpydanticを使ってタイプレベルで辞書の形を強制できます — 複雑なデータ構造を持つ大規模なコードベースには、より良い長期的な答えです。

