例外の捕捉・後処理・再送出
読み取り元を閉じる処理を使い、正常入力、変換失敗、ゼロ除算を比較します。例外の捕捉順と再送出を変更し、呼出元に何が伝わるか確認します。
説明
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
import iodef 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)print("result", read_ratio("3"))closed True result 4.0
print("result", read_ratio("x"))closed True result invalid
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
def classify(raw):
try:
return int(raw)
except Exception:
return "general"
except ValueError:
return "invalid"print(classify("x"))general
def parse_logged(raw, log):
try:
return int(raw)
except ValueError:
log.append(("rejected", raw))
raiselog = []try:
parse_logged("x", log)
except ValueError as error:
print(type(error).__name__, log)ValueError [('rejected', 'x')]演習完了