2026年7月7日
2026年7月7日
Pythonのエラーハンドリングとデバッグ方法|トレースバックの読み方・try/exceptの書き方・pdbの使い方
Python でエラーが出たときは、まずトレースバックの「最後の行」で例外の種類とメッセージを確認し、その少し上にある「自分のコードの行」を探すのが最短の手順です。原因が分かったら、想定できる失敗だけを try/except で具体的に受け止め、想定外のものはトレースバックごとログに残します。この記事では、トレースバックの読み方、よく出る例外の原因と確認方法、例外処理の書き方、そして breakpoint() と pdb を使ったデバッグの進め方を説明します。
トレースバックの読み方
次のようなエラーが出たとします。
Traceback (most recent call last):
File "/home/user/app/main.py", line 12, in <module>
total = summarize(rows)
File "/home/user/app/report.py", line 8, in summarize
return sum(r["price"] for r in rows)
File "/home/user/app/report.py", line 8, in <genexpr>
return sum(r["price"] for r in rows)
KeyError: 'price'
読む順番は下からです。
- 最後の行:
KeyError: 'price'が例外の種類とメッセージです。「辞書にpriceというキーが無かった」ことが分かります - その直前のブロック:エラーが実際に起きた場所です。
report.pyの8行目 - 上に向かって:そこに至るまでの呼び出しの流れです。
main.pyの12行目からsummarize()が呼ばれています
外部ライブラリの中でエラーが起きた場合、最後のほうにはライブラリ内部のファイルが並びます。その場合は、上にさかのぼって自分のコードの最後の行を探し、そこでライブラリに何を渡したかを確認します。原因はたいてい、渡した値のほうにあります。
最近の Python はエラーメッセージが改善されており、「もしかして(Did you mean ...?)」の候補や、式の中のどの部分でエラーが起きたかを示す ^^^^ の印が表示されることがあります。これらもヒントとして活用してください。
例外が連鎖している場合は、「During handling of the above exception, another exception occurred」や「The above exception was the direct cause of the following exception」という行をはさんで、複数のトレースバックが表示されます。最初の原因は、一番上のトレースバックにあります。
よく出る例外と確認方法
出会う頻度の高いものから順に、原因の確認方法と対処を挙げます。
ModuleNotFoundError: No module named '...'
ライブラリがインストールされていないか、インストールした環境と実行している環境が違うことが原因です。後者が特に多く、仮想環境を有効にし忘れている、pip と python が別の Python を指している、といったケースです。python -m pip install パッケージ名 のように、実行に使う python 経由で pip を呼ぶと食い違いを防げます。python -c "import sys; print(sys.executable)" で、実際に使われている Python の場所を確認できます。
AttributeError: 'NoneType' object has no attribute '...'
ある値が None なのに、属性やメソッドを使おうとしています。関数が return を書き忘れていて None を返している、検索結果が見つからず None が返った(正規表現の re.match() など)、list.sort() のように戻り値が None のメソッドの結果を代入した、などが典型です。エラーの行の1つ前で、その変数がどこから来たかをたどります。
KeyError / IndexError
辞書に無いキー、リストの範囲外の番号を参照しています。外部から受け取るデータ(API の応答、CSV、設定ファイル)でよく起こります。キーが無いこともあり得るなら d.get("price") や d.get("price", 0) を使い、無いことが異常なら、どのデータで欠けていたかをログに残してから例外を出します。
TypeError
型が合っていません。"合計: " + 100 のような文字列と数値の連結、関数の引数の数の誤りなどです。メッセージに期待された型と実際の型が書かれているので、そのまま読めば原因が分かることがほとんどです。print(type(x), repr(x)) で値の中身を確かめます。
UnicodeDecodeError / UnicodeEncodeError
ファイルの文字コードと、読み込むときに指定した(または既定の)文字コードが一致していません。open() で encoding を省略すると OS ごとの既定値が使われるため、Windows と Linux で結果が変わります。open(path, encoding="utf-8") のように明示し、Shift_JIS のファイルなら encoding="cp932" を指定します。
IndentationError / SyntaxError
文法の誤りで、プログラムは1行も実行されません。指摘された行だけでなく、その前の行の閉じ忘れたかっこや引用符が原因であることも多いので、少し上まで確認します。タブとスペースが混在していると IndentationError になるため、エディターでスペースに統一する設定にしておくと防げます。
例外処理の書き方
捕まえる例外は具体的に
import json
import logging
logger = logging.getLogger(__name__)
def load_config(path: str) -> dict:
try:
with open(path, encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
logger.warning("設定ファイルが無いため既定値を使います: %s", path)
return {}
except json.JSONDecodeError as e:
raise ValueError(f"設定ファイルの形式が不正です: {path}") from e
else:
logger.info("設定を読み込みました: %s", path)
return data
finally:
logger.debug("load_config 終了")
この例では次の考え方で書いています。
- 想定できる失敗だけを受け止める:ファイルが無いのは起こり得ることなので既定値で続行し、形式が不正なのは設定の誤りなので、分かりやすいメッセージの例外にして呼び出し元へ伝えます
- **
except Exception:や素のexcept:で何でも握りつぶさない**:プログラムの誤りまで隠れてしまい、原因の分からない不具合になります。素のexcept:は Ctrl+C(KeyboardInterrupt)まで捕まえてしまいます - **
raise ... from eで元の例外をつなぐ**:トレースバックに元の原因が残り、調査しやすくなります - **
elseとfinally**:elseは例外が起きなかったときだけ、finallyは例外の有無にかかわらず最後に実行されます。ファイルや接続の後片付けは、with文を使えばfinallyを書かずに済みます
プログラムの入口で最後に受け止める
想定外の例外は、途中で握りつぶさずに上へ伝え、プログラムの入口で1回だけ受け止めてトレースバックごと記録します。
import logging
import sys
def main() -> int:
...
return 0
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
try:
sys.exit(main())
except Exception:
logging.getLogger(__name__).exception("想定外のエラーで終了しました")
sys.exit(1)
終了コードを 1 にしておくと、cron や systemd、CI の側で失敗として検知できます。ログの出力先やローテーションの設定は、別記事の logging モジュールの解説を参照してください。
独自の例外クラスを作る
アプリ固有の失敗を表すには、Exception を継承した独自の例外クラスを作ります。class PaymentError(Exception): pass のように基底クラスを1つ作り、その下に細かな種類を作っておくと、呼び出し側は except PaymentError: でまとめて受け止められます。Python 3.11 以降では、e.add_note("注文ID: 123") で例外に補足情報を付けることもでき、トレースバックの末尾に表示されます。
デバッガ(pdb)で止めて調べる
print() を何か所にも入れて調べるより、デバッガで処理を止めて変数を直接見るほうが早いことが多くあります。止めたい行に breakpoint() と書いて実行すると、その位置で標準のデバッガ pdb が起動します。
def summarize(rows):
breakpoint() # ここで停止する
return sum(r["price"] for r in rows)
停止中に使う主なコマンドは次のとおりです。
p 式:値を表示する(例:p rows[0])n:次の行へ進む(関数の中には入らない)s:関数の中に入るc:次のブレークポイントまで実行を続けるl:周辺のソースコードを表示するw:現在の呼び出しの流れ(スタック)を表示するq:終了する
エラーが起きた時点の状態を調べたい場合は、python -m pdb script.py で実行すると、例外で止まったときにその場でデバッガに入れます(事後デバッグ)。VS Code や PyCharm などのエディターのデバッガを使えば、同じことを画面上の操作で行えます。
breakpoint() を消し忘れると、本番で処理が止まって入力待ちになります。コミット前に検索して取り除くか、環境変数 PYTHONBREAKPOINT=0 を設定して無効にしておきます。
開発中は python -X dev script.py で実行する「開発モード」も役に立ちます。通常は表示されない警告(非推奨の機能の使用、閉じ忘れたファイルなど)が表示されるようになり、問題を早めに見つけられます。
エラー処理で気をつけたいこと
エラーメッセージやログには、パスワードや API キー、個人情報を含めないようにします。例外のメッセージに受け取ったデータをまるごと入れると、意図せず漏れることがあります。また、Web アプリで利用者にトレースバックをそのまま見せると、内部の構成が知られる手がかりになります。利用者には簡潔なメッセージだけを返し、詳細はサーバーのログに残します。
エラーメッセージの表示形式やデバッガの機能は、Python のバージョンが上がるごとに少しずつ改善されています(2026年10月現在)。この記事の add_note() は 3.11 以降、それ以外は 3.8 以降で使える内容です。
まとめ
- トレースバックは下から読む。最後の行で例外の種類を確認し、自分のコードの最後の行を探す
- ModuleNotFoundError は環境の食い違い、NoneType の AttributeError は値の出どころをたどる
exceptは具体的な例外に絞り、想定外のものは入口でlogger.exception()を使って記録するraise ... from eで原因をつなぎ、終了コードで失敗を外部に伝える- 調査は
breakpoint()と pdb、python -X devを活用し、消し忘れはPYTHONBREAKPOINT=0で防ぐ