エラノート エラノート

Python KeyErrorの原因と対処法まとめ

Python KeyError 辞書 エラー解決 デバッグ
広告スペース (article-top)

PythonのKeyErrorは、辞書(dict)に存在しないキーでアクセスしようとしたときに発生するエラーです。データ処理やAPI連携など辞書を扱う場面は多く、避けて通れないエラーの一つです。この記事では、KeyErrorの発生パターンと具体的な対処法をコード例とともに解説します。

KeyErrorとは

KeyErrorは、辞書から存在しないキーの値を取得しようとしたときにPythonが発生させる例外です。辞書はキーと値のペアでデータを管理する構造ですが、指定したキーが辞書に含まれていないと処理を続行できません。

エラーメッセージの読み方

user = {"name": "太郎", "age": 25}
print(user["email"])
# KeyError: 'email'

エラーメッセージにはアクセスしようとしたキー名が表示されます。この例では辞書user"email"というキーが存在しないためエラーになっています。

よくある発生場面

KeyErrorは以下のような場面で多く発生します。

  • APIレスポンスのJSONデータから値を取り出すとき
  • CSVファイルを辞書形式で読み込んで処理するとき
  • 設定ファイルの値を参照するとき
  • ユーザー入力に基づいて辞書を検索するとき

パターン1: 存在しないキーへの直接アクセス

最も基本的なKeyErrorのパターンです。

# NG: 存在しないキーに直接アクセス
config = {"host": "localhost", "port": 8080}
print(config["database"])
# KeyError: 'database'

getメソッドで安全にアクセスする

辞書のgetメソッドを使うと、キーが存在しない場合にデフォルト値を返すことができます。

# OK: getメソッドを使う
config = {"host": "localhost", "port": 8080}
database = config.get("database")
print(database)  # None

# デフォルト値を指定する
database = config.get("database", "mydb")
print(database)  # "mydb"

in演算子で事前にチェックする

キーの存在をin演算子で確認してからアクセスする方法もあります。

# OK: in演算子で事前チェック
config = {"host": "localhost", "port": 8080}
if "database" in config:
    print(config["database"])
else:
    print("databaseキーが見つかりません")

パターン2: キー名のスペルミスや大文字小文字の違い

キー名が微妙に異なっていてKeyErrorになるケースです。

# NG: キー名のスペルミス
response = {"status_code": 200, "message": "OK"}
print(response["statusCode"])
# KeyError: 'statusCode'

Pythonの辞書ではキー名の大文字小文字やアンダースコアの有無が厳密に区別されます。

キー一覧を確認して修正する

# デバッグ: 辞書のキー一覧を確認
response = {"status_code": 200, "message": "OK"}
print(response.keys())
# dict_keys(['status_code', 'message'])

# 正しいキー名でアクセス
print(response["status_code"])  # 200

API連携での注意点

外部APIから受け取るJSONデータはキー名の規則がサービスごとに異なります。キャメルケース(statusCode)とスネークケース(status_code)が混在していることもあるため、レスポンスの構造を事前に確認しましょう。

import json

# APIレスポンスの構造を確認
response_text = '{"statusCode": 200, "userName": "Taro"}'
data = json.loads(response_text)
print(json.dumps(data, indent=2))
# キー名を確認してからアクセス
print(data["statusCode"])  # 200

パターン3: ネストした辞書でのKeyError

辞書が入れ子になっている場合、途中のキーが存在しないとKeyErrorになります。

# NG: ネストした辞書のアクセスでエラー
user = {
    "name": "太郎",
    "address": {
        "city": "東京"
    }
}
print(user["address"]["zipcode"])
# KeyError: 'zipcode'

段階的にチェックする

# OK: 段階的にキーの存在を確認
user = {
    "name": "太郎",
    "address": {
        "city": "東京"
    }
}

if "address" in user and "zipcode" in user["address"]:
    print(user["address"]["zipcode"])
else:
    print("郵便番号が登録されていません")

try-exceptで処理する

深くネストした辞書の場合は、try-exceptを使うほうがコードが簡潔になることがあります。

# OK: try-exceptでKeyErrorを捕捉
user = {
    "name": "太郎",
    "address": {
        "city": "東京"
    }
}

try:
    zipcode = user["address"]["zipcode"]
    print(f"郵便番号: {zipcode}")
except KeyError as e:
    print(f"キー {e} が見つかりません")
    # キー 'zipcode' が見つかりません

パターン4: forループ中のKeyError

辞書のリストを処理するとき、一部のデータにキーが欠けているとKeyErrorが発生します。

# NG: データの一部にキーが欠けている
users = [
    {"name": "太郎", "email": "[email protected]"},
    {"name": "花子"},  # emailキーがない
    {"name": "次郎", "email": "[email protected]"}
]

for user in users:
    print(user["email"])
# 1回目は成功、2回目でKeyError: 'email'

getメソッドでループを安全にする

# OK: getメソッドでデフォルト値を設定
users = [
    {"name": "太郎", "email": "[email protected]"},
    {"name": "花子"},
    {"name": "次郎", "email": "[email protected]"}
]

for user in users:
    email = user.get("email", "未登録")
    print(f"{user['name']}: {email}")
# 太郎: [email protected]
# 花子: 未登録
# 次郎: [email protected]

データの前処理で欠損を補完する

大量のデータを扱う場合は、ループの前にデータを整形しておく方法が効果的です。

# OK: 事前にデフォルト値を設定
default_user = {"name": "不明", "email": "未登録", "age": 0}

users = [
    {"name": "太郎", "email": "[email protected]"},
    {"name": "花子"},
]

for user in users:
    complete_user = {**default_user, **user}
    print(f"{complete_user['name']}: {complete_user['email']}")
# 太郎: [email protected]
# 花子: 未登録

パターン5: defaultdictで自動的にデフォルト値を設定する

標準ライブラリのcollections.defaultdictを使うと、存在しないキーへのアクセス時にデフォルト値が自動的に生成されます。

カウント処理での活用

from collections import defaultdict

# 通常の辞書だとKeyErrorになる
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]

# NG: 通常の辞書
counts = {}
for word in words:
    counts[word] += 1  # KeyError: 'apple'(初回アクセス時)

# OK: defaultdictを使う
counts = defaultdict(int)
for word in words:
    counts[word] += 1  # 存在しないキーは自動的に0で初期化

print(dict(counts))
# {'apple': 3, 'banana': 2, 'cherry': 1}

グルーピング処理での活用

from collections import defaultdict

# データをカテゴリ別にグルーピング
items = [
    ("果物", "りんご"),
    ("野菜", "にんじん"),
    ("果物", "みかん"),
    ("野菜", "キャベツ"),
]

groups = defaultdict(list)
for category, item in items:
    groups[category].append(item)

print(dict(groups))
# {'果物': ['りんご', 'みかん'], '野菜': ['にんじん', 'キャベツ']}

パターン6: setdefaultメソッドの活用

setdefaultメソッドは、キーが存在しない場合にデフォルト値を設定しつつ値を返します。

# setdefaultの使い方
config = {"host": "localhost"}

# キーがなければ設定して返す
port = config.setdefault("port", 8080)
print(port)    # 8080
print(config)  # {'host': 'localhost', 'port': 8080}

# キーが既にあれば既存の値を返す
host = config.setdefault("host", "0.0.0.0")
print(host)    # 'localhost'(既存の値が維持される)

getとsetdefaultの使い分け

config = {"host": "localhost"}

# get: 辞書を変更しない(読み取り専用の場面に適する)
value = config.get("port", 8080)
print(config)  # {'host': 'localhost'} ← 辞書は変わらない

# setdefault: 辞書にデフォルト値を追加する(初期化の場面に適する)
value = config.setdefault("port", 8080)
print(config)  # {'host': 'localhost', 'port': 8080} ← 辞書が更新される

まとめ

KeyErrorは辞書に存在しないキーへアクセスしたときに発生します。対処法を整理すると以下の通りです。

  • getメソッドでデフォルト値を指定して安全にアクセスする
  • in演算子でキーの存在を事前にチェックする
  • try-exceptでKeyErrorを捕捉して代替処理を行う
  • defaultdictsetdefaultで自動的にデフォルト値を設定する
  • キー名のスペルミスや大文字小文字の違いに注意する

辞書を使う場面では「このキーは必ず存在するか」を常に意識することが、KeyErrorを防ぐ最も確実な方法です。

広告スペース (article-bottom)

あわせて読みたい