一覧PG-04-2 · 条件分岐

ガード節と早期リターン

在庫の引当てを入れ子とガード節で実装し、同じ入力の結果を比較します。条件式と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

1
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)
2
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))]
3
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)
4
[(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)]
5
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
6
def dispatch(command, stock): match command: case ["status"]: return ("stock", stock) case ["reserve", q]: return reserve(q, stock) case _: return ("unknown", stock)
7
[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)]
8
def misplaced(q, stock): result = reserve(q, stock) return result print("reserved", result)
9
print("returned", misplaced(2, 3))
returned ('ok', 1)

演習完了