ガード節と早期リターン
在庫の引当てを入れ子とガード節で実装し、同じ入力の結果を比較します。条件式とmatchでも処理を選び、途中で終了する経路と到達しない行を確かめます。
説明
分岐を重ねた処理は、条件に合わない場合を先に返すガード節へ整理できます。ただし、型や範囲を調べる順番を変えると、先に例外が起きる場合もあります。
在庫の引当てを入れ子とガード節で実装し、同じ入力の結果を比較します。条件式とmatchでも処理を選び、途中で終了する経路と到達しない行を確かめます。
1入れ子で入力と在庫を検査
在庫は非負の整数です。個数はNone、整数以外、0以下、在庫超過の順に検査し、有効なら引当て後の在庫を返します。
def nested(q, stock):個数と在庫を受け取る
if q is not None:未指定を除外
if type(q) is int:整数そのものを要求
if q > 0:正の個数
if q <= stock:在庫内
return ("ok", stock - q)成功時だけ差を計算
else:
return ("shortage", stock)
else:
return ("range", stock)
else:
return ("type", stock)
else:
return ("missing", stock)戻り値は結果名と残在庫の組です。Noneを先に除き、型を確認してから大小比較します。type(q) is intはこの例でboolを個数として受け付けないための条件です。
2失敗の種類と正常処理
在庫3で、未指定、文字列、真偽値、0、超過4、正常2を試します。
cases = [None, "2", True, 0, 4, 2]異なる検査へ進む入力; [(q, nested(q, 3)) for q in cases]結果名と残在庫を比較失敗した入力では在庫3が維持され、2個の引当てだけ残在庫1になります。最も外のelseは未指定、内側のelseはそれぞれ対応する条件の失敗です。字下げが対応関係を決めます。
3同じ検査をガード節へ整理
続行できない条件ごとにreturnします。成功処理は最後へ置きます。
def reserve(q, stock):
if q is None:未指定で早期終了
return ("missing", stock)
if type(q) is not int:型が違えば早期終了
return ("type", stock)
if q <= 0:範囲外で早期終了
return ("range", stock)
if q > stock:不足で早期終了
return ("shortage", stock)
return ("ok", stock - q)全条件を満たす場合最後へ到達した時点で、正の整数かつ在庫以下だと分かります。検査の順番を保ったまま入れ子を浅くしています。個数が文字列のままq<=0を先に実行するとTypeErrorになるため、順序にも意味があります。
4整理前後の結果を照合
同じ6入力で二つの関数の戻り値を比較します。
[(repr(q), nested(q, 3), reserve(q, 3)同じ入力で新旧を実行, nested(q, 3) == reserve(q, 3)戻り値の一致) for q in cases同じ試験入力]すべて一致します。今回の入力で振舞いを保てていることを確認できました。すべての型や値を網羅した証明ではないので、仕様にある境界や追加の入力も確認対象になります。
5条件式は選んだ側だけを評価する
未指定Noneの場合に引算しない条件式を使います。続けて正常な個数2でも確認します。
q = None; stock = 3未指定の状態; print(stock - q if q is not None else stock選ばれた側だけ計算); q = 2有効な個数; print(stock - q if q is not None else stock引算する側を選択)Noneではelse側だけが選ばれて3、2では引算して1です。条件式は選ばれなかった側を評価しません。ただし、この式だけでは文字列や負数など他の不正入力を除けないため、reserveの全検査の代わりにはなりません。
6命令の形に応じて処理する
statusという一要素の命令と、reserve・個数という二要素の命令をmatchで扱います。個数の検査はreserveへ任せます。
def dispatch(command, stock):
match command:命令の形を照合
case ["status"]:一要素の状態照会
return ("stock", stock)
case ["reserve", q]:二要素から個数を取得
return reserve(q, stock)同じ入力検査を利用
case _:それ以外
return ("unknown", stock)matchは文字列の一致だけでなく、要素数や並びも照合します。qは一致した二番目の要素を受け取る名前です。最後の_はどのパターンにも該当しない命令を受けます。
7命令の形と引数の誤り
正常な命令、個数が不適切な命令、要素が足りない命令を続けて実行します。
[dispatch(c, 3)命令と在庫を渡す for c in [["status"], ["reserve", 2], ["reserve", -1], ["reserve", True], ["reserve"], ["delete", 1]]正常・範囲・型・構造の入力]statusは在庫、reserveと2は成功、-1はrange、Trueはtypeです。要素が不足したreserveやdeleteはunknownになります。構造の照合と引数値の検査を分担しています。
8returnの後の行は実行されない
成功時の記録をreturnより後に置いた関数を定義します。
def misplaced(q, stock):
result = reserve(q, stock)検査と引当て
return resultここで呼出元へ戻る
print("reserved", result)到達しない行printはreturnの後にあるため、どの入力でも実行されません。文法上書けることと、実行される経路があることは別です。記録が必要なら、戻る前の適切な位置へ置きます。
9表示されない記録を確認
正常入力で呼び出し、returnedを付けて呼出元の表示と分けます。
print("returned", misplaced(2, 3))正常入力の戻り値と実行範囲returnedと結果だけで、reservedは出ません。分岐や早期終了を整理するときは、戻り値に加えて必要な記録や更新が実行される位置も確認します。
まとめ
型と範囲の検査順を保ち、続けられない条件をガード節で先に返します。
条件式・match・returnで、選ばれる処理と到達できない行を確認します。
操作履歴と解説を振り返れます。
Python 3
def nested(q, stock):
if q is not None:
if type(q) is int:
if q > 0:
if q <= stock:
return ("ok", stock - q)
else:
return ("shortage", stock)
else:
return ("range", stock)
else:
return ("type", stock)
else:
return ("missing", stock)cases = [None, "2", True, 0, 4, 2]; [(q, nested(q, 3)) for q in cases][(None, ('missing', 3)), ('2', ('type', 3)), (True, ('type', 3)), (0, ('range', 3)), (4, ('shortage', 3)), (2, ('ok', 1))]def reserve(q, stock):
if q is None:
return ("missing", stock)
if type(q) is not int:
return ("type", stock)
if q <= 0:
return ("range", stock)
if q > stock:
return ("shortage", stock)
return ("ok", stock - q)[(repr(q), nested(q, 3), reserve(q, 3), nested(q, 3) == reserve(q, 3)) for q in cases][('None', ('missing', 3), ('missing', 3), True), ("'2'", ('type', 3), ('type', 3), True), ('True', ('type', 3), ('type', 3), True), ('0', ('range', 3), ('range', 3), True), ('4', ('shortage', 3), ('shortage', 3), True), ('2', ('ok', 1), ('ok', 1), True)]q = None; stock = 3; print(stock - q if q is not None else stock); q = 2; print(stock - q if q is not None else stock)3 1
def dispatch(command, stock):
match command:
case ["status"]:
return ("stock", stock)
case ["reserve", q]:
return reserve(q, stock)
case _:
return ("unknown", stock)[dispatch(c, 3) for c in [["status"], ["reserve", 2], ["reserve", -1], ["reserve", True], ["reserve"], ["delete", 1]]][('stock', 3), ('ok', 1), ('range', 3), ('type', 3), ('unknown', 3), ('unknown', 3)]def misplaced(q, stock):
result = reserve(q, stock)
return result
print("reserved", result)print("returned", misplaced(2, 3))returned ('ok', 1)演習完了