条件指定漏れをチェックできる便利関数assert_never()
This work is licensed under a
Creative Commons Attribution 4.0 International License
.
はじめに
自己紹介
-
Ryuji Tsutsui@ryu22e
-
さくらインターネット株式会社所属
-
Python歴は14年くらい(主にDjango)
-
Python Boot Camp、Shonan.pyなどコミュニティ活動もしています
-
著書(共著):『Python実践レシピ』
【PR】『Python実践レシピ』第2版が出ます!
3月16日発売!
assert_never()関数の話
typingモジュールのassert_never()関数を使うと、if文やパターンマッチの条件指定漏れを型チェッカーで検出できて便利!という話をします。
まず、こんな列挙型を定義する
"""assert_never_example.py"""
import enum
class Color(enum.Enum):
RED = 0
BLUE = 1
YELLOW = 2
Colorをパターンマッチで判定するコードを書く
# (省略)
def get_color_name_jp(color: Color) -> str:
match color:
case Color.RED:
return "赤"
case Color.BLUE:
return "青"
# Color.YELLOWを書き忘れている
case _:
return "Unknown"
print(get_color_name_jp(Color.RED)) # 「赤」
print(get_color_name_jp(Color.BLUE)) # 「青」
print(get_color_name_jp(Color.YELLOW)) # 「黄」ではなく「Unknown」
assert_never_example.pyの実行結果
% python assert_never_example.py
赤
青
Unknown
こんな時便利なのがassert_never()関数
from typing import assert_never # (1)これを追加
# (省略)
def get_color_name_jp(color: Color) -> str:
match color:
case Color.RED:
return "赤"
case Color.BLUE:
return "青"
# Color.YELLOWを書き忘れている
case _:
assert_never(color) # (2)ここを変更
# (省略)
型チェッカーが条件指定漏れを検出してくれる
% mypy assert_never_example.py
assert_never_example.py:17: error: Argument 1 to "assert_never" has incompatible type "Literal[Color.YELLOW]"; expected "Never" [arg-type]
Found 1 error in 1 file (checked 1 source file)
このコードを実行するとどうなるか?
assert_never()関数がAssertionErrorを送出する。
% python assert_never_example.py
赤
青
Traceback (most recent call last):
File "/***/assert_never_example.py", line 21, in
print(get_color_name_jp(Color.YELLOW))
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File "/***/assert_never_example.py", line 17, in get_color_name_jp
assert_never(color) # ここを変更
~~~~~~~~~~~~^^^^^^^
File "/***/typing.py", line 2582, in assert_never
raise AssertionError(f"Expected code to be unreachable, but got: {value}")
AssertionError: Expected code to be unreachable, but got:
条件指定漏れの検出って、「型チェック」なの?
という疑問は、次の説明を聞けば解消するはず。
assert_never()関数と同じ機能の関数を自作する
assert_never()関数と同じく、引数がNever型の関数を定義すればよい。
from typing import Never
class UnreachableError(Exception):
pass
# ↓引数の型をNeverにするのがポイント
def assert_unreachable(arg: Never) -> Never:
raise UnreachableError(arg)
Never型って何?
-
Never型は「ボトム型」 -
ボトム型とは、型理論や数理論理学において値を持たない型のこと
-
ざっくり説明すると、「どんな値を入れても型チェックでエラーになる型」
以下はすべて型チェックでエラーになる
"""never_example.py"""
from typing import Never
a: Never = 1 # NG
b: Never = "test" # NG
c: Never = True # NG
d: Never = None # NG
前述のコードを型チェック
% mypy never_example.py
never_example.py:3: error: Incompatible types in assignment (expression has type "int", variable has type "Never") [assignment]
never_example.py:4: error: Incompatible types in assignment (expression has type "str", variable has type "Never") [assignment]
never_example.py:5: error: Incompatible types in assignment (expression has type "bool", variable has type "Never") [assignment]
never_example.py:6: error: Incompatible types in assignment (expression has type "None", variable has type "Never") [assignment]
つまり、どういうことか
-
assert_never()関数の引数の型はNever -
つまり、
assert_never()関数が呼ばれる条件がある場合、引数に指定した値は必ず型チェックエラーになる -
条件指定漏れ用の特別なチェックが走っているのではなく、あくまで型チェックを行っている
最後に
まとめ
-
assert_never()関数は、条件指定漏れを検出してくれる便利関数 -
引数を
Never型にした関数を定義すれば、assert_never()と同じ機能の関数を作れる -
この機能は、
Never型の特徴を利用した型チェックにより実現している