Pythonの例外処理を理解する:try・except・as・raiseの基本
概要
Pythonでは、プログラムの実行中に発生した問題を例外として表現します。
例えば、次の処理では、文字列を整数に変換できないため ValueError が発生します。
int("abc")
例外処理では、主に次の構文を使用します。
| 構文 | 役割 |
|---|---|
try |
例外が発生する可能性のある処理を書く |
except |
発生した例外を捕捉して処理する |
as |
発生した例外インスタンスを変数で受け取る |
else |
例外が発生しなかった場合に実行する |
finally |
例外の有無にかかわらず実行する |
raise |
意図的に例外を発生させる |
例外処理は、エラーを隠すためではなく、異常な状態を適切な場所へ伝え、処理方法を分離するために使用する。
実施条件
- Pythonの関数を理解していること
- クラスとインスタンスの基本を理解していること
-
if文による条件分岐を理解していること
環境
| ツール | バージョン | 目的 |
|---|---|---|
| Python | 3.12 | Pythonコードの実行 |
| VS Code | 任意 | コード編集 |
Pythonの例外とは
例外は、プログラムの実行を通常どおり続けられない状態を表します。
number = int("abc")
実行すると、次のような例外が発生します。
ValueError: invalid literal for int() with base 10: 'abc'
例外が捕捉されなければ、通常はその時点で処理が中断され、トレースバックが表示されます。
例外もクラスである
ValueError は、Pythonに組み込まれた例外クラスです。
error = ValueError("値が不正です")
error は、ValueError クラスのインスタンスです。
print(type(error))
# <class 'ValueError'>
print(isinstance(error, ValueError))
# True
print(isinstance(error, Exception))
# True
例外の大まかな継承関係は、次のようになっています。
BaseException
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── TypeError
├── ValueError
├── LookupError
│ ├── KeyError
│ └── IndexError
├── OSError
│ └── FileNotFoundError
└── RuntimeError
通常のアプリケーションで扱う例外の多くは、Exception を継承しています。
代表的な組み込み例外
TypeError
処理対象の型が不適切な場合に発生します。
"100" + 20
# TypeError
関数内で型を検証して発生させることもできます。
def set_price(price: int) -> None:
if not isinstance(price, int):
raise TypeError(
"priceは整数で指定してください"
)
ValueError
型は適切でも、値の内容が不正な場合に発生します。
int("abc")
# ValueError
def set_price(price: int) -> None:
if price < 0:
raise ValueError(
"priceは0以上で指定してください"
)
TypeError と ValueError の違いは次のとおりです。
| 状態 | 使用する例外 |
|---|---|
| 値の型が不適切 | TypeError |
| 型は正しいが値が不適切 | ValueError |
KeyError
辞書に存在しないキーを参照した場合に発生します。
product = {
"name": "Python入門",
}
print(product["price"])
# KeyError: 'price'
IndexError
リストやタプルの範囲外を参照した場合に発生します。
items = [1, 2, 3]
print(items[10])
# IndexError
FileNotFoundError
存在しないファイルを開こうとした場合に発生します。
open("missing.txt")
# FileNotFoundError
tryとexcept
例外が発生する可能性のある処理を try に書きます。
例外が発生した場合の処理を except に書きます。
raw_value = "abc"
try:
number = int(raw_value)
except ValueError:
print("整数へ変換できませんでした")
出力:
整数へ変換できませんでした
処理の流れは次のとおりです。
tryブロックを実行
↓
例外が発生したか
├── しなかった → exceptを飛ばす
└── 発生した → 一致するexceptを探す
except ... as exc
as を使用すると、発生した例外インスタンスを変数で受け取れます。
try:
number = int("abc")
except ValueError as exc:
print(exc)
出力:
invalid literal for int() with base 10: 'abc'
ここでの各要素は、次の役割を持ちます。
| 要素 | 意味 |
|---|---|
ValueError |
捕捉する例外クラス |
as |
例外インスタンスに変数名を付ける |
exc |
発生した例外インスタンス |
exc は一般的な命名であり、別の名前でも構いません。
except ValueError as error:
print(error)
ただし、exc または error がよく使われます。
as excだけでは表示されない
次のコードでは、例外インスタンスを exc で受け取っていますが、何も表示していません。
try:
int("abc")
except ValueError as exc:
pass
as exc は、例外を自動的に表示する構文ではありません。
表示する場合は、明示的に print() します。
print(exc)
ログに記録する場合は、ロガーへ渡します。
import logging
logger = logging.getLogger(__name__)
try:
int("abc")
except ValueError as exc:
logger.exception(
"整数への変換に失敗しました"
)
例外インスタンスの内容
try:
int("abc")
except ValueError as exc:
print(type(exc))
print(str(exc))
print(exc.args)
出力例:
<class 'ValueError'>
invalid literal for int() with base 10: 'abc'
("invalid literal for int() with base 10: 'abc'",)
| 記述 | 内容 |
|---|---|
type(exc) |
例外インスタンスのクラス |
str(exc) |
例外メッセージ |
exc.args |
例外生成時に渡された引数 |
複数のexcept
発生する可能性がある例外ごとに、異なる処理を書けます。
def get_product_price(
product: dict[str, str],
) -> int:
try:
return int(product["price"])
except KeyError:
print("priceキーがありません")
except ValueError:
print("priceを整数へ変換できません")
return 0
get_product_price({})
# priceキーがありません
get_product_price({"price": "三千円"})
# priceを整数へ変換できません
複数の例外をまとめて捕捉する
同じ処理を行う場合は、タプルで複数の例外を指定できます。
try:
price = int(product["price"])
except (KeyError, ValueError) as exc:
print(f"商品価格を取得できません: {exc}")
exceptの順番
例外クラスには継承関係があるため、具体的な例外から先に記述します。
try:
int("abc")
except ValueError:
print("値が不正です")
except Exception:
print("その他の例外です")
次の順番は適切ではありません。
try:
int("abc")
except Exception:
print("例外が発生しました")
except ValueError:
print("値が不正です")
ValueError は Exception のサブクラスです。
そのため、最初の except Exception ですでに捕捉され、後ろの except ValueError には到達しません。
raise
raise は、プログラム側から意図的に例外を発生させる構文です。
raise ValueError("値が不正です")
関数内の入力検証でよく使用します。
def calculate_discount(
price: int,
discount_rate: float,
) -> int:
if price < 0:
raise ValueError(
"priceは0以上で指定してください"
)
if not 0 <= discount_rate <= 1:
raise ValueError(
"discount_rateは0以上1以下で指定してください"
)
return round(price * (1 - discount_rate))
calculate_discount(3000, 1.5)
# ValueError
例外クラスと例外インスタンス
次のどちらも使用できます。
raise ValueError
raise ValueError()
メッセージを渡す場合は、例外インスタンスを生成して送出します。
raise ValueError(
"価格は0以上で指定してください"
)
原因を確認しやすくするため、通常は具体的なメッセージを付けます。
引数なしのraise
except ブロック内で、引数を指定せずに raise すると、現在処理中の例外をそのまま再送出します。
def parse_price(raw_price: str) -> int:
try:
return int(raw_price)
except ValueError as exc:
print(f"価格変換に失敗しました: {exc}")
raise
try:
parse_price("三千円")
except ValueError:
print("呼び出し元で例外を処理しました")
処理の流れは次のとおりです。
int()でValueError
↓
parse_price()内のexcept
↓
ログや補助処理
↓
raiseで同じValueErrorを再送出
↓
呼び出し元のexcept
ログを記録したうえで、処理自体は上位層に任せたい場合に利用できます。
else
else は、try 内で例外が発生しなかった場合に実行されます。
try:
price = int("3000")
except ValueError as exc:
print(f"変換失敗: {exc}")
else:
print(f"変換成功: {price}")
出力:
変換成功: 3000
次のように、例外が発生する可能性のある処理だけを try に含められます。
try:
price = int(raw_price)
except ValueError as exc:
print(f"変換失敗: {exc}")
else:
save_price(price)
save_price() で発生した例外まで、誤って ValueError として処理することを避けられます。
finally
finally は、例外が発生したかどうかにかかわらず実行されます。
try:
price = int("3000")
except ValueError:
print("変換失敗")
finally:
print("処理を終了します")
出力:
処理を終了します
リソースの解放などに使用できます。
file = open("sample.txt", encoding="utf-8")
try:
content = file.read()
finally:
file.close()
ただし、ファイル操作では通常、with を使う方が簡潔です。
with open("sample.txt", encoding="utf-8") as file:
content = file.read()
try・except・else・finally
完全な構造は次のとおりです。
try:
price = int("3000")
except ValueError as exc:
print(f"変換失敗: {exc}")
else:
print(f"変換成功: {price}")
finally:
print("処理終了")
| ブロック | 実行条件 |
|---|---|
try |
最初に実行される |
except |
対象の例外が発生した場合 |
else |
例外が発生しなかった場合 |
finally |
原則として必ず実行される |
避けるべき例外処理
例外の握りつぶし
try:
process()
except Exception:
pass
このコードでは、例外が発生しても何も記録されません。
障害の発見や原因調査が難しくなります。
少なくともログを記録するか、再送出します。
try:
process()
except ValueError as exc:
print(f"処理に失敗しました: {exc}")
raise
裸のexcept
try:
process()
except:
print("エラー")
例外クラスを省略すると、KeyboardInterrupt や SystemExit なども捕捉する可能性があります。
可能な限り、具体的な例外を指定します。
except ValueError:
複数の通常例外を最後にまとめて処理する必要がある場合は、Exception を指定します。
except Exception as exc:
ただし、広範囲な Exception の捕捉は、アプリケーションの最上位層など、責任範囲が明確な場所に限定します。
tryの範囲が広すぎる
try:
price = int(raw_price)
product = create_product(price)
save_product(product)
send_notification(product)
except ValueError:
print("値が不正です")
どの処理で ValueError が発生したのか分かりにくくなります。
例外が想定される範囲を小さくします。
try:
price = int(raw_price)
except ValueError as exc:
print(f"価格が不正です: {exc}")
return
product = create_product(price)
save_product(product)
send_notification(product)
活用例:商品価格の変換
def parse_price(raw_price: str) -> int:
try:
price = int(raw_price)
except ValueError as exc:
print(
f"価格を整数へ変換できません: {exc}"
)
raise
else:
if price < 0:
raise ValueError(
"価格は0以上で指定してください"
)
return price
finally:
print("価格変換処理を終了しました")
try:
result = parse_price("3000")
except ValueError as exc:
print(f"入力エラー: {exc}")
else:
print(f"変換結果: {result}")
出力:
価格変換処理を終了しました
変換結果: 3000
まとめ
- 例外は、通常処理を継続できない状態を表すオブジェクト
-
ValueErrorやTypeErrorなどもクラスである -
tryに例外が発生する可能性のある処理を書く -
exceptで特定の例外を捕捉する -
as excは、例外インスタンスを変数として受け取る -
as excだけでは、例外は表示されない -
print(exc)やロガーを使って、例外内容を出力する -
raiseにより意図的に例外を発生させられる - 引数なしの
raiseは、現在の例外を再送出する -
elseは例外がなかった場合に実行される -
finallyは例外の有無にかかわらず実行される - 例外は具体的なクラスから捕捉する
- 例外を何もせず握りつぶす処理は避ける