一覧PG-10-2 · 例外とエラー処理

例外の捕捉・後処理・再送出

読み取り元を閉じる処理を使い、正常入力、変換失敗、ゼロ除算を比較します。例外の捕捉順と再送出を変更し、呼出元に何が伝わるか確認します。

説明

exceptはtry内から伝わった特定の型の例外を扱います。finallyは正常終了や例外による終了の際に共通の後処理を実行します。捕捉した例外を再び呼出元へ伝えることもできます。

読み取り元を閉じる処理を使い、正常入力、変換失敗、ゼロ除算を比較します。例外の捕捉順と再送出を変更し、呼出元に何が伝わるか確認します。

1後処理をfinallyへ置く

StringIOを読み取り資源として使い、整数へ変換して12を割ります。ValueErrorだけを回復対象にします。

import io文字列の読み取り元を用意

ioは文字列をファイルのように扱うために使います。続けて関数を定義し、読み取り元のclosedで解放を確認します。

2変換失敗だけを捕捉する関数

最後のfinallyで読み取り元を閉じ、closedを表示します。

def read_ratio(raw): source = io.StringIO(raw)読み取り元を開く try:捕捉する範囲 value = int(source.read())読み取りと整数変換 return 12 / value計算して戻る except ValueError:変換の失敗を回復 return "invalid" finally:終了時の共通処理 source.close()読み取り元を閉じる print("closed", source.closed)解放を確認

returnで正常に戻る場合も、捕捉した変換失敗で戻る場合もfinallyを通ります。ZeroDivisionErrorはこのexceptの対象ではないので、閉じた後に呼出元へ伝わります。

3正常終了でも閉じる

整数3で呼び出します。

print("result", read_ratio("3"))正常な呼出しと後処理の順序

closed Trueが先に出て、その後に呼出元のresult 4.0が出ます。returnの値を計算してからfinallyを実行し、最後に呼出元へ戻ります。

4捕捉できる失敗でも閉じる

数字でないxを渡します。

print("result", read_ratio("x"))変換失敗からの回復

ValueErrorが捕捉されてinvalidになります。後処理のclosed Trueも出ています。exceptが動いたことと、資源を閉じることを別々の場所へ記述しています。

5捕捉しない例外でも閉じる

0はintへ変換できますが、割算時に失敗します。

read_ratio("0")捕捉対象外の例外

closed Trueの後にZeroDivisionErrorが表示されます。finallyは例外を無かったことにする処理ではありません。finallyにreturnを書いて元の例外を隠すような構造は避けます。

6広い例外型を先に置いた場合

ExceptionをValueErrorより先に捕捉する関数を定義します。

def classify(raw): try: return int(raw) except Exception:先に一致する広い型 return "general"広い型の処理 except ValueError:到達しない具体型 return "invalid"具体型に書いた処理

ValueErrorはExceptionの一種なので、先の広いハンドラーに一致します。型に基づいて上から最初の一致を選ぶため、後ろのValueError用の枝へ到達しません。

7入力と捕捉先を照合

数字でない入力をclassifyへ渡します。

print(classify("x"))ValueErrorが入る捕捉先

generalになります。具体的な失敗ごとに回復するなら、具体型を先に並べ、予期しない例外を広く回復扱いにしないようにします。

8記録してから同じ例外を再送出

ValueErrorの記録を追加してから、引数なしraiseで呼出元へ伝える関数を定義します。

def parse_logged(raw, log): try: return int(raw)変換を試みる except ValueError:変換失敗を捕捉 log.append(("rejected", raw))必要な記録 raise同じ例外を再送出

exceptが末尾まで進むと関数がNoneを返す形になる場合があります。ここではraiseによって失敗を明示的に伝え、呼出元が成功値と誤認することを防ぎます。

9外側で受け取って記録を確認する

呼出元のtryで再送出されたValueErrorを受け取り、型と記録を表示します。

log = []呼出元の記録

空の記録を用意しました。次のtry内からの呼出しが、外側のexceptの対象になります。

10再送出された型と元の入力

外側で想定した型だけを捕捉します。

try: parse_logged("x", log)内側の処理 except ValueError as error:再送出された型を捕捉 print(type(error).__name__, log)例外と記録を照合

ValueErrorとrejectedの記録が両方確認できます。記録する層と、回復方法を決める呼出元の層を分けても、元の失敗を保って伝えられます。

まとめ

exceptは型と記述順で選ばれ、finallyは正常・例外双方の後処理に使います。

記録後も失敗を伝える場合は再送出し、成功扱いで握りつぶしません。

操作履歴と解説を振り返れます。

Python 3

1
import io
2
def read_ratio(raw): source = io.StringIO(raw) try: value = int(source.read()) return 12 / value except ValueError: return "invalid" finally: source.close() print("closed", source.closed)
3
print("result", read_ratio("3"))
closed True
result 4.0
4
print("result", read_ratio("x"))
closed True
result invalid
5
read_ratio("0")
closed True
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "<console>", line 5, in read_ratio
ZeroDivisionError: division by zero
6
def classify(raw): try: return int(raw) except Exception: return "general" except ValueError: return "invalid"
7
print(classify("x"))
general
8
def parse_logged(raw, log): try: return int(raw) except ValueError: log.append(("rejected", raw)) raise
9
log = []
10
try: parse_logged("x", log) except ValueError as error: print(type(error).__name__, log)
ValueError [('rejected', 'x')]

演習完了