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

エラーの返し方と独自例外

個数の文字列を解析し、0をエラー値にする方式と、成功フラグを返す方式を比較します。独自例外には元の入力を保持し、集計時に欠落した件数も確認します。

説明

失敗を通常の戻り値へ混ぜると、正当な値と区別できなくなる場合があります。例外を捕捉して処理を続ける場合も、拒否した入力を残さなければ結果の完全性を判断できません。

個数の文字列を解析し、0をエラー値にする方式と、成功フラグを返す方式を比較します。独自例外には元の入力を保持し、集計時に欠落した件数も確認します。

10を失敗の印にも使う関数

数字に変換できないとき、0を返す関数を作ります。

def parse_or_zero(text): try: return int(text)成功した値 except ValueError: return 0失敗にも0を使う

個数0を正当な値として許すなら、変換失敗にも0を使うと意味が衝突します。次に呼出元から見える結果を比較します。

2成功0と失敗が同じに見える

0とbadをそれぞれ解析します。

print(parse_or_zero("0"), parse_or_zero("bad"))成功と失敗の衝突

どちらも0で、戻り値だけでは変換できたか分かりません。結果に0が含まれることを、正常入力があった証拠としては使えません。

3成功フラグと値を分ける

成功ならTrueと整数、失敗ならFalseとNoneを返します。

def parse_result(text): try: return True, int(text)成功と値 except ValueError: return False, None失敗を別の形で返す

戻り値を二要素にすることで、正当な0を保持できます。呼出元は数値を使う前に成功フラグを確認する必要があります。

4成功の0を保持する

同じ二入力を新しい関数へ渡します。

print(parse_result("0"), parse_result("bad"))状態と値を別々に確認

(True, 0)と(False, None)になりました。Noneを数量として後続の計算へ渡さないよう、成功確認を利用側の処理へ含めます。

5独自例外に入力を持たせる

ValueErrorを継承し、raw属性に拒否した入力を保持します。

class InvalidCount(ValueError):個数の失敗を表す型 def __init__(self, raw): self.raw = raw元の入力を保持 super().__init__("invalid count")基底例外の初期化

メッセージを解析せず、例外の型とrawから元の入力を取得できます。呼出元はこの型を指定して捕捉でき、他の種類の失敗と別の処理を選べます。

6変換失敗の原因も保持して伝える

intのValueErrorを独自例外へ変換し、fromで元の原因へ対応付けます。負数も不正な個数として拒否します。

def parse_count(text): try: count = int(text)文字列の解析 except ValueError as error: raise InvalidCount(text) from error原因と入力を保持して送出 if count < 0: raise InvalidCount(text)変換後の範囲検査 return count有効な個数

文字列の変換失敗には元のValueErrorが原因として残ります。負数は変換成功後の範囲違反なので、ここでは別の原因例外を付けていません。

7型・入力・原因を取り出す

badと-1で、利用側が得られる情報を確認します。

for text in ("bad", "-1"): try: parse_count(text)二種類の失敗を発生 except InvalidCount as error:用途ごとの型で捕捉 print(type(error).__name__, error.raw, type(error.__cause__).__name__)型・入力・原因

両方InvalidCountですが、rawは各入力を保持します。原因は変換失敗ならValueError、負数の範囲違反ではNoneTypeです。必要なら失敗理由専用の属性も設計できます。

8拒否した位置を残して集計する

0、bad、3、-1、4の五入力を処理し、有効値と拒否記録へ分けます。

texts = ["0", "bad", "3", "-1", "4"]成功0を含む五入力; accepted = []; rejected = []成功と拒否の受取先; print(texts)対象件数の基準

全件を必須とするなら失敗で中断する設計もあります。ここでは部分集計を認め、その代わり拒否位置と入力を返せるようにします。

9失敗を空のexceptで捨てない

成功だけを加算対象にし、例外は位置付きで記録します。

for index, text in enumerate(texts):入力の位置を保持 try: accepted.append(parse_count(text))成功した値を追加 except InvalidCount as error:想定した不正入力 rejected.append((index, error.raw))拒否位置と入力を保存

拒否を握りつぶすと、3+4の7が全五件を正常に処理した合計に見えます。拒否位置を残すことで、何件が計算へ含まれたか確認できます。

10合計と処理件数を照合する

合計だけでなく、成功・拒否・元の件数を表示します。

print(sum(accepted), accepted, rejected)合計と採否の内訳; print(len(accepted), len(rejected), len(texts), len(accepted) + len(rejected) == len(texts))全入力の処理件数

合計7、成功3件、拒否2件で五入力すべての処理先が分かります。正当な0も成功側へ残っています。エラーの伝え方は、呼出元が欠落を判断できる情報まで含めて設計します。

まとめ

成功値と衝突しない失敗表現を選び、利用側で確認します。

処理を続ける場合は拒否した入力と件数を残し、部分結果を全件成功と扱いません。

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

Python 3

1
def parse_or_zero(text): try: return int(text) except ValueError: return 0
2
print(parse_or_zero("0"), parse_or_zero("bad"))
0 0
3
def parse_result(text): try: return True, int(text) except ValueError: return False, None
4
print(parse_result("0"), parse_result("bad"))
(True, 0) (False, None)
5
class InvalidCount(ValueError): def __init__(self, raw): self.raw = raw super().__init__("invalid count")
6
def parse_count(text): try: count = int(text) except ValueError as error: raise InvalidCount(text) from error if count < 0: raise InvalidCount(text) return count
7
for text in ("bad", "-1"): try: parse_count(text) except InvalidCount as error: print(type(error).__name__, error.raw, type(error.__cause__).__name__)
InvalidCount bad ValueError
InvalidCount -1 NoneType
8
texts = ["0", "bad", "3", "-1", "4"]; accepted = []; rejected = []; print(texts)
['0', 'bad', '3', '-1', '4']
9
for index, text in enumerate(texts): try: accepted.append(parse_count(text)) except InvalidCount as error: rejected.append((index, error.raw))
10
print(sum(accepted), accepted, rejected); print(len(accepted), len(rejected), len(texts), len(accepted) + len(rejected) == len(texts))
7 [0, 3, 4] [(1, 'bad'), (3, '-1')]
3 2 5 True

演習完了