16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】`typing.NoReturn`の使いどころ

Last updated at Posted at 2023-06-07

チーム内で質問があった、かつNoReturnについて『ロバストPython』に言及がなかったので書いてみた。

TL; DR

  • 公式ドキュメントの「関数が返り値を持たない」とは「エラーがraiseされるとき」を意味する
  • ただ単にreturnしていない、returnで返すオブジェクトをコードベースで書いていないだけならNoneをアノテーションする
  • 「この関数を呼び出せば絶対にエラーになる」ときにNoReturnを使う
  • Tを返すが分岐によってはエラーになる」ときはUnion[T, NoReturn]ではなくTを使う

不適切な使い方

from typing import NoReturn, Union


def hoge(a: str) -> NoReturn:  # <- `None`が正しい!
    return  # 明示して書いていないだけで、`None`が返されている


def fuga(a: str) -> NoReturn:  # <- `None`が正しい!
    print(a)  # 明示してreturnしていないだけで、`None`が返されている


def moge(a: str) -> Union[str, NoReturn]:  # <- `str`が正しい!
    # - `str`と並列に`NoReturn`を配置しているが、エラーは`str`と違って返すものではなく送出するもの
    # - 全ての関数はメモリ不足など不可抗力で実行時エラーになる可能性がある
    # - エラーが起こる可能性を論じなきゃいけないなら全部`Union[NoReturn, T]`になってしまう
    # - エラー型が`RuntimeError`であることは型アノテーションでは伝わらない
    if a == "foo":
        raise RuntimeError
    return a

適切な使い方

from typing import Literal, NoReturn, overload


def hoge() -> NoReturn:
    raise RuntimeError


# `overload`を使って引数のパターンによっては例外が送出されることを表現
@overload
def moge(a: Literal["foo"]) -> NoReturn: ...
@overload
def moge(a: str) -> str: ...

def moge(a: str) -> str:
    if a == "foo":
        raise RuntimeError
    return a

まとめ

NoReturnという、否定形で表現されていて返り値でアノテーションされているのが意図が伝わりづらい原因な気がします。
同様のコンセプトを表現可能なNeverは引数へアノテーションしていて、使いどころをより明確にしているのでそのうち紹介記事を書こうと思います。

16
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?