mypyでPythonの型チェックをしてみた
今回はPythonの型チェックをできるmypyを使ってみたので紹介します。
mypyとは?
mypyはPythonの静的型チェックツールになります。Pythonは動的型付言語であり、データ型の厳密なチェックが基本的にされません。実装の難易度を下げて柔軟なコーディングができる反面、データ型の間違いによるエラーが発生するかどうかが実行するまでわからないという弱点があります。mypyを使うことにより、静的型チェックを実施することができるので、データ型の扱い方のミスなどを事前に気づくことができます。
使ってみる
インストール
uv
を利用して以下のようにインストールします。
uv new mypy_test -p 3.12 && mypy_test
uv add mypy
簡単なサンプルコードを作ってみる
まず最初に、以下のようなサンプルコードを作ってみます。
def add(x: int, y: int) -> int:
return x + y
x = 10
y = 20
z = add(x, y)
print(f"{z=}")
add
関数はxとy二つのint型データを受け取り、その足し算をint型で返すというものを用意しました。まずはx、y両方にint型を設定した場合、つまり望んだデータ型を受け取る場合のチェックをしてみます。
mypyを適用するには以下のようにすることで対応できます。その結果、問題はなくSuccess
と表示されました。
uv run mypy simple_function.py
# 結果
Success: no issues found in 1 source file
次は試しにxの値をx = 10.5
のようにしてfloat型にしたときにどのような結果になるかみてみました。その結果、add
関数の呼び出しでxは本来int型となるはずだがfloat型を受け取ったと検知できています。
simple_func.py:7: error: Argument 1 to "add" has incompatible type "float"; expected "int" [arg-type]
Found 1 error in 1 file (checked 1 source file)
それではadd
関数の戻り値をreturn float(x + y)
のようにしたらどのような結果になるかみてみます。実行してみると、戻り値はint型を期待しているのにfloat型になっていることが検知できました。
simple_func.py:2: error: Incompatible return value type (got "float", expected "int") [return-value]
Found 1 error in 1 file (checked 1 source file)
typingの使い方
Pythonには組み込みのtypingモジュールがあり、アノテーションをする場合はうまく使うことで可読性が上がります。
例えばリストの値の合計を計算したいとします。愚直に実装すれば以下のようになります。
def _sum(x: list[int]) -> int:
return sum(x)
x = [i for i in range(10)]
sum_result = _sum(x)
もちろんこれはアノテーションが正しいのでエラーが発生することはありません。
uv run mypy sum_sequence.py
# 結果
Success: no issues found in 1 source file
ここで、例えばlistではなくtupleを使うことに変更した場合エラーになります(tupleの方がlistと比較してメモリ使用量などが小さくなります)。
def _sum(x: list[int]) -> int:
return sum(x)
x = tuple([i for i in range(10)])
sum_result = _sum(x)
uv run mypy sum_sequence.py
# 結果
sum_sequence.py:6: error: Argument 1 to "_sum" has incompatible type "tuple[int, ...]"; expected "list[int]" [arg-type]
Found 1 error in 1 file (checked 1 source file)
listを使うことで確定している場合は元の実装が良いですが、シーケンスが指定されることを想定する場合、例えば以下のようにするとtupleでもlistでも受け入れることができるようになります。
from typing import Sequence
def _sum(x: Sequence[int]) -> int:
return sum(x)
x = tuple([i for i in range(10)])
sum_result = _sum(x)
uv run mypy sum_sequence.py
# 結果
Success: no issues found in 1 source file
Anyの利用について
PythonでOSSなどをみたりすると、しばしばtyping.Any
が利用されているのを目にします。Any
を指定するとどのような値が帰ってくる可能性があることを明示できますが、正直あまり利用しない方がいいと思っています。なぜかというと、Any
というアノテーションはアノテーションをしているようで一切の情報を与えることができないからです。
そのため、mypyでAnyが使われていれば検知させたいですが、デフォルトでは検知されません。mypyでAnyの利用を検知させるには--disallow-any-expr
オプションを指定します。例えば先ほどの例でSequenceをAnyにしてみます。
from typing import Any
def _sum(x: Any) -> int:
return sum(x)
x = tuple([i for i in range(10)])
sum_result = _sum(x)
オプションなしで実行すると以下のようにエラーは検知されません。
uv run mypy sum_sequence_any.py
# 結果
Success: no issues found in 1 source file
それでは--disallow-any-expr
をつけて実行すると以下のように検知されます。
uv run mypy sum_sequence_any.py --disallow-any-expr
# 結果
sum_sequence_any.py:4: error: Expression has type "Any" [misc]
Found 1 error in 1 file (checked 1 source file)
まとめ
今回はmypyを使ったデモを簡単にお見せしました。Pythonは動的型付け言語でありデータ型に関する厳密なチェックが実施されにくい傾向にあるかと思います。Pythonを利用される方はぜひmypyなどの型チェッカーをぜひ利用してみてはいかがでしょうか。
Discussion