Menu
日本語

Python例外処理入門: try/except/finally/raiseの使い方

Pythonでのエラーハンドリングを基礎から解説。try/except/finallyの書き方、特定の例外をキャッチする方法、raiseで自分で例外を投げる方法までまとめました。

エラーは「機嫌の悪い値」みたいなもの

Pythonで何かがうまくいかないとき——ゼロ除算、存在しないファイルの読み込み、数値への変換失敗など——ランタイムは 例外 (exception) オブジェクトを生成し、誰かがそれをキャッチするまでコールスタックを遡っていきます。最後まで誰もキャッチしなければ、プログラムは停止してトレースバックを吐き出すわけです。

例外そのものは悪者ではありません。「この処理はもう続けられません。理由はこれです」とPythonが知らせてくれる仕組みです。ですから、どの例外は自分で復旧できて、どれはそのまま上に投げるべきか——これをケースバイケースで判断するのが、私たちの仕事になります。

python 例外処理の基本形

main.py
Output
Click Run to see the output here.
  • try: で、失敗するかもしれないコードのブロックを始めます。
  • except ValueError: は、try の中で該当する例外が発生したときだけキャッチします。
  • 例外が発生しなければ、except の部分はまるごとスキップされます。

スニペットに 42 を渡せば普通に動きます。hello を渡すと、ハンドラ側が動くというわけです。

特定の例外だけをキャッチする

Python の例外クラスは階層構造になっています。よく出会うものをいくつか挙げておきます。

  • ValueError — 値そのものがおかしいケース(int("abc") や範囲外の引数など)。
  • TypeError — 型が合っていないケース("hi" + 3 など)。
  • KeyError — 辞書に指定したキーが存在しないとき。
  • IndexError — シーケンスのインデックスが範囲外のとき。
  • FileNotFoundError — ファイルが見つからないとき。
  • ZeroDivisionError — ゼロ除算をしたとき。
  • AttributeError — オブジェクトに指定した属性が無いとき。

対処の仕方がわかっている例外だけをピンポイントでキャッチしましょう。

main.py
Output
Click Run to see the output here.

タプルで複数の例外をまとめて指定すれば、一つの except 節でまとめてキャッチできます。

main.py
Output
Click Run to see the output here.

as e の部分に注目してください。これで例外オブジェクトを e に束縛できるので、メッセージや属性を後から確認できます。

すべてをキャッチしてはいけない

except: だけで受けると、本当に何でもかんでもキャッチしてしまいます。KeyboardInterrupt(Ctrl-C による中断)やシステム終了まで巻き込むので、使わないでください。

except Exception: は少しマシですが、それでも危険です。想定外のバグまで飲み込んでしまい、本当の原因を隠してしまいます。

# Don't do this without a really good reason.
try:
    do_something()
except Exception:
    pass

基本的には「自分が対処できるとわかっている例外だけをキャッチする」のが正解です。想定外の例外がプログラムの最上位まで飛んでしまった場合でも、トレースバックを見れば何が起きたかが一目でわかります。これはバグではなく、むしろありがたい仕様です。

else と finally

try 文には、さらに2つのオプション節があります。

  • else は、try ブロックが例外を発生させずに正常終了したときに実行されます。
  • finally は、例外の有無に関わらず必ず実行されます。
main.py
Output
Click Run to see the output here.

else は「成功したときだけ動かしたい処理」を書くのにぴったりな場所です。try ブロックには、本当に失敗する可能性のある処理だけを入れておきたいですよね。一方 finally は、try が失敗しても必ず実行してほしい後片付け用です。たとえばリソースのクローズ、ロックの解放、状態の復元などですね。

raise で例外を自分から投げる

自分のコードでエラーを知らせたいときは raise を使います:

main.py
Output
Click Run to see the output here.

何が起きたのかに合ったエラー型を選びましょう。まずは ValueErrorTypeErrorFileNotFoundError といった組み込みの例外から検討し、自作クラスに手を出すのはその後です。

カスタム例外を定義する

組み込みの例外では意味がうまく伝わらないときは、独自の例外クラスを用意します。

main.py
Output
Click Run to see the output here.

Exception(あるいはもっと具体的な組み込み例外)を継承して、クラスにdocstringを付けておく。基本的にはこれだけでOKです。カスタム例外を作っておくと、呼び出し側は「自分のドメインにとって意味のあるエラーだけ」をピンポイントでキャッチできます。

raise ... from ...:例外の連鎖

ある例外がきっかけで別の例外が発生したときは、その連鎖を残しておきましょう。

main.py
Output
Click Run to see the output here.

from e を付けると、元の例外が連鎖情報として紐づきます。トレースバックを出力したとき、表に出た ConfigError と、その原因となった FileNotFoundError の両方が表示されるわけです。デバッグのときにこの履歴があるのは本当に助かります。

コンテキストマネージャー:後始末をもっとスマートに

finally でも十分役目は果たせますが、ファイルのようなリソースを扱う場合は コンテキストマネージャーwith 文で使うあれです)のほうがほぼ間違いなく優れています。

# finally version
f = open("data.txt")
try:
    data = f.read()
finally:
    f.close()

# with version
with open("data.txt") as f:
    data = f.read()

どちらでも安全です。with構文の方が短く書けて、後片付けも自動でやってくれます。finallyを持ち出すのは、標準ライブラリがコンテキストマネージャーでラップしてくれていない処理を自分で書くときだけにしましょう。

例外をキャッチすべきでないとき

例外をキャッチするというのは、「自分でこのエラーに対処できる」という 意思表示 です。対処できないなら、素直に上位へ伝播させましょう。次のようなコードは、ほぼ間違いなくアンチパターンです。

try:
    do_work()
except Exception:
    pass  # silently ignore everything

エラーを握りつぶすと、バグが見えなくなります。中途半端な状態でダマシダマシ動かすくらいなら、派手にクラッシュさせたほうがマシです。

まとめ

  • try/except は、回復可能なエラーを処理するために使う。
  • Exception でざっくり捕まえず、具体的な例外をキャッチする。
  • 自分のコードでエラーを知らせたいときは raise を使う。
  • finally によるクリーンアップの多くは、with ブロックで置き換えられる。
  • 迷ったら、例外はそのまま上に伝播させる。

次回は、Python でよく遭遇する具体的なエラーたち —— KeyErrorValueErrorModuleNotFoundError など —— を一通り見ていきます。あわせて、それらをサッと直すためのデバッグの習慣も紹介します。

よくある質問

Pythonでエラーを処理するにはどうすればいい?

失敗する可能性のある処理を try ブロックで囲み、except で具体的な例外を捕まえます。例えば try: risky() except ValueError: ... のように書きます。else は例外が出なかったときだけ実行され、finally は例外の有無に関わらず必ず実行されるので、後片付け処理に使います。

念のため Exception を全部キャッチしておけば安全では?

いいえ、それはやめた方がいいです。except:except Exception: で何でも捕まえてしまうと、想定外のバグまで握りつぶしてしまいます。自分が対処できる例外だけを具体的にキャッチし、それ以外は伝播させて本当の問題に気付けるようにしましょう。

raise と raise from の違いは?

raise NewError(...) は新しい例外を発生させるだけですが、raise NewError(...) from original と書くと元の例外を「原因」として紐付けてくれて、トレースバックにも表示されます。低レベルのエラーを受けて、より分かりやすい高レベルの例外として投げ直したいときに from を使うのが定番です。

Coddyでコードを学ぼう

始める