チーム内で質問があった、かつ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
は引数へアノテーションしていて、使いどころをより明確にしているのでそのうち紹介記事を書こうと思います。