概要
mypyを使ったPythonでの各種型チェックの方法についてまとめる。
最近はPythonでも型指定を真面目にやっている例を見かけことが増えてきた感がある。
バージョン情報
- Python3.7.3
- mypy==0.720
参考URL
PEP 484 -- Type Hints https://www.python.org/dev/peps/pep-0484/
導入
mypyをインストールしておく。
$ pip install mypy
引数と戻り値の型指定
下記の例はstrを引数に取ってintを返す例。
def int_value(var: str) -> int: return int(var) int_value("10")
この関数に文字列ではなくintを引数に渡してみる。
int_value(10)
型指定はpythonの実行自体に何かしら影響するものではないので、このコードを実行しても特にエラーになることはない。
これをmypyでチェックしてみる。上記コードをファイル名 foo.py に保存しているものとする。
$ mypy foo.py
#=> foo.py:5: error: Argument 1 to "int_value" has incompatible type "int"; expected "str"
int_value関数はstrを期待しているけどintが渡された旨が警告で出ている。
変数の型指定
変数に対して型指定を行う例。
下記のように間違った型指定をしてmypyを実行すると、型の違反を通知してくれる。
foo: int = 'str' bar: str = 0 #=> foo.py:1: error: Incompatible types in assignment (expression has type "str", variable has type "int") #=> foo.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str")
--strictで厳密なチェック
下記のように戻り値の指定がない関数を書いた場合。
def bar(s:str): return int(s)
普通にmypyを実行しても特に指摘はされない。
$ mypy foo.py
--strictを引数に入れるとretrun typeが指定されていないと警告が出る。
$ mypy foo.py --strict #=> foo.py:1: error: Function is missing a return type annotation
listやdictの型指定
list, dict, tupleを型指定する。
lst: list = [1, 2, 3] dic: dict = {'foo': 'bar'} tpl: tuple = ('a', 1)
それぞれの要素の型も指定してみる。エラーが出るように代入する型は指定と違うものにする。
import typing lst: typing.List[int] = ['a', 'b'] dic: typing.Dict[str, str] = {1: 2} tpl: typing.Tuple[str, int] = (3, 'z')
mypyを実行すると型の違う代入に対してそれぞれ警告が表示される。
foo.py:3: error: List item 0 has incompatible type "str"; expected "int" foo.py:3: error: List item 1 has incompatible type "str"; expected "int" foo.py:4: error: Dict entry 0 has incompatible type "int": "int"; expected "str": "str" foo.py:5: error: Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "Tuple[str, int]")
同じ型が頻出する場合に毎回 typing.List[int] のように記述するのは面倒なので、よく使う型は別名をつけておくと楽。
import typing Vector = typing.List[float] vec: Vector = [1.0, 2.1]
lambdaの型指定
lambdaの型指定をする場合はCallableを使う。
下記は引数でstrを1つ受け取り、intを返すlambdaの例。
import typing f: typing.Callable[[str], int] = lambda s: int(s)
型が決まっていない場合
Anyを指定しておくとどんな型でも入れられる。
import typing foo: typing.Any = 10 foo = 'bar'
numpy等のライブラリをimportしてエラーが出る場合
numpyなどのライブラリをimportするとstubないぞと警告が出ます。
import numpy as np #=> foo.py:1: error: No library stub file for module 'numpy' #=> foo.py:1: note: (Stub files are from https://github.com/python/typeshed)
この手の警告は --ignore-missing-imports を付けておけば全体的に防げます。
mypy --ignore-missing-imports foo.py
但し上記指定では全体的にimport先のチェックが行われなくなってしまいます。
たとえば下記のような存在しないモジュールへのimportが入っていた場合は、デフォルトではそんなモジュールないぞと警告を出してくれる。
import hoge #=> foo.py:1: error: Cannot find module named 'hoge' #=> foo.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
指定したライブラリだけチェックをスキップしたい場合は、mypy.init というファイル名で下記のような記述をします。
[mypy] [mypy-numpy] ignore_missing_imports = True
上記はnumpyであればignore_missing_importsを適用する、という意味の記述になります。
これで下記のコードに対して mypy foo.py を実行すると、hogeの方だけ警告が出てnumpyのimportに対しては警告が出ない。
import numpy as np import hoge #=> foo.py:2: error: Cannot find module named 'hoge' #=> foo.py:2: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
指定行だけチェックをしない
特定のコメントを記述することで、指定行のチェックを抑制することもできます。
import numpy as np # type: ignore import hoge def bar(s:str) -> int: return int(s) bar(10) # type: ignore
type ignoreを付けた場合と除去した場合で、mypy --strictでの実行結果を比較してみる。
type ignoreなしの場合、1行目のnumpyのimport、2行目のhogeのimport、7行目でstring指定されている引数にintを入れている箇所の3行で警告が出る。
foo.py:1: error: No library stub file for module 'numpy' foo.py:1: note: (Stub files are from https://github.com/python/typeshed) foo.py:2: error: Cannot find module named 'hoge' foo.py:2: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports foo.py:7: error: Argument 1 to "bar" has incompatible type "int"; expected "str"
type ignoreありの場合は、numpyのimportと7行目の型チェックがスキップされる。
foo.py:2: error: Cannot find module named 'hoge' foo.py:2: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
改定履歴
Author: Masato Watanabe, Date: 2019-08-24, 記事投稿