条件指定漏れを​チェックできる​便利関数assert_never()

Creative Commons License 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版が​出ます!

https://amzn.asia/d/0jaaWbIX

Amazon URLのQRコード

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型の​特徴を​利用した​型チェックに​より​実現している

ご清聴​ありがとう​ございました