エラーの返し方と独自例外
個数の文字列を解析し、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
def parse_or_zero(text):
try:
return int(text)
except ValueError:
return 0print(parse_or_zero("0"), parse_or_zero("bad"))0 0
def parse_result(text):
try:
return True, int(text)
except ValueError:
return False, Noneprint(parse_result("0"), parse_result("bad"))(True, 0) (False, None)
class InvalidCount(ValueError):
def __init__(self, raw):
self.raw = raw
super().__init__("invalid count")def parse_count(text):
try:
count = int(text)
except ValueError as error:
raise InvalidCount(text) from error
if count < 0:
raise InvalidCount(text)
return countfor 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
texts = ["0", "bad", "3", "-1", "4"]; accepted = []; rejected = []; print(texts)['0', 'bad', '3', '-1', '4']
for index, text in enumerate(texts):
try:
accepted.append(parse_count(text))
except InvalidCount as error:
rejected.append((index, error.raw))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
演習完了