概要
前回 は型ヒントを使って事前に型チェックする方法を紹介しました
今回は TypeGuard を紹介します
簡単に言えば型をチェックする関数を独自で定義することができる機能です
環境
- macOS 11.6.4
- Python 3.10.2
サンプルコード
- vim app.py
from dataclasses import dataclass
from typing import TypeGuard
@dataclass
class User():
name: str
age: int
def is_user(user: User) -> TypeGuard[User]:
if isinstance(user, User):
return True
return False
def print_user(user: User):
if is_user(user):
print(user.name)
print(user.age)
else:
print("The argument type must be the User class.")
if __name__ == '__main__':
user = User("hawk", 10)
print_user(user)
print_user("hawk")
解説
TypeGuard を使っているのは is_user 関数です
戻り値のアノテーションを TypeGuard[User] にし戻り値を boolean にすることで指定の引数が User クラスかどうかを判定する独自の TypeGuard 関数であることを明示しています
あとは型チェックしたい箇所 (print_user) で is_user を呼び出します
実際にこのコードを実行するとエラーなく終了してしまいますがこのあと紹介する型チェックツールを使うことで事前に型エラーを検知することができるようになります
型チェック
過去の記事では mypy というチェックツールを紹介しました
今回は pyright を使ってみました
mypy でも同じように型チェックは可能です
pyright というツールがあるのでこれを使って型チェックしてみます
- pip install pyright
- pyright app.py
No configuration file found.
No pyproject.toml file found.
stubPath /Users/hawk/Documents/work/try_python/typings is not a valid directory.
Assuming Python platform Darwin
Searching for source files
Found 1 source file
/Users/hawk/Documents/work/try_python/app.py
/Users/hawk/Documents/work/try_python/app.py:28:16 - error: Argument of type "Literal['hawk']" cannot be assigned to parameter "user" of type "User" in function "print_user"
"Literal['hawk']" is incompatible with "User" (reportGeneralTypeIssues)
1 error, 0 warnings, 0 informations
Completed in 0.779sec
hawk という文字列は User クラスではないというエラーが表示されています
こんな感じで事前チェックでは型が違うよという警告がちゃんと表示されます
最後に
Typing を使うと事前に型チェックすることが可能になるので関数に意図しないオブジェクトを渡すことを防ぐことができます
便利な機能ではありますがアノテーションや NewType ヘルパーを使う必要が出てくるのでコードは増えがちです
そもそも型あり言語を使ったほうがいいような気もしますが動的型付けとハイブリットで使いたい場合にはいいのかもしれません
また実際に実行しただけだとエラーにはならないので Typing を使う場合には IDE や mypy のように CI などで事前チェックするような機構が必要になるかなと思います