【Python】mypyモジュールで型注釈(型ヒント)のチェックをする


投稿日 2023年5月6日 >> 更新日 2023年5月13日

概要

mypyモジュールを実行するに当たって、型注釈のつけ方について簡単な説明をします。

型注釈が定義されたPythonファイルを使用して、mypyを実行していきます。

mypyのオプション機能を「pyproject.toml」に定義して、デフォルト値の振る舞いを変更します。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.10.0
使用ライブラリ ライセンス
mypy==1.2.0 MIT

Pythonの型注釈とは

mypyモジュールを使うことで、自作モジュール等のソースコードに定義されている型注釈のチェックを行うことができます。

型注釈とは、Pythonのバージョン3.0から取り入れられたオプションで、Pythonでは「型ヒント」とも呼ばれたりします。

主に「変数」や「関数の引数とその戻り値」にPythonの型を注釈として与えます。

「型」とは型オブジェクトを指していて、「int」や「str」の事を言います。

変数や関数に注釈を付けることによって、開発に関わる人がソースコードをみるだけで内容が明確になったりバグを防ぐこともできます。

なので、必ずしも付けないといけないわけではありませんがソフトウェア開発の質は向上します。

変数に注釈を与える場合は、変数の右側に「:(コロン)」を添えてからPythonの「型(タイプ)」を与えます。

# 変数の場合
num: int

Pythonの辞書(dict)のように、「キー:バリュー」のイメージで変数に注釈を付けていくと理解しやすいです。

「num:int」となっている場合は値が整数である必要があるため、整数を代入することが最もです。

num: int = 1

関数に注釈を与える場合は、引数の右側に「:(コロン)」を添えてからPythonの「型(タイプ)」を与えます。

# 関数の場合
def func(arg1: int, arg2: str):
    pass

デフォルト値を設定する場合は、「引数:型」に続けて設定します。

# 関数の場合
def func(arg1: int=1, arg2: str='a'):
    pass

関数戻り値の注釈を与える場合は、引数の括弧を閉じた後に「->」を添えてから型を与えます。

# 関数戻り値の場合
def func(arg1: int=1, arg2: str='a') -> str:
    return f'{arg1}(arg1)は整数型で{arg2}(arg2)は文字列型です'

戻り値に示した型注釈は、関数自体の処理が終わった後に型注釈による処理が実行されるみたいです。

戻り値(return等)が無い場合は、「Nonetype」を与えます。

# 関数戻り値の場合
def func(arg1: int=1, arg2: str='a') -> None:
    print(f'{arg1}(arg1)は整数型で{arg2}(arg2)は文字列型です')

上記の型注釈の方法は、クラスオブジェクトを作成する際も有効です。

# クラスのコンストラクタ戻り値の場合
class Obj(object):
    def __init__(self, name: str) -> None:
        self.name = name

型注釈の使い方を簡単に説明してきましたが、Python標準では「int」や「str」といった柔軟性のない「型」しかありません。

そこでPython標準ライブラリの「typing」モジュールを使うとより柔軟性のある「型」を利用できるようになります。

# パイプを使って「int」と「str」に対応
def func(arg1: int | str) -> int | str:
    return arg1

from typing import Any

# Anyオブジェクトを使って全ての型に対応
def func2(arg1: Any) -> Any:
    return arg1

「typing」モジュールの使用方法についてはこちら「 typing --- 型ヒントのサポート」をご参照ください。

mypyの実装

pipでインストールをします。

$ pip install mypy

「mypy」モジュールの公式ドキュメントはこちらです。

「mypy」モジュールのオプション機能は膨大にあるので、その中でも一部の機能を使って実行していきます。

以下のソースコードを使って「mypy」モジュールでチェックしていきます。

# my_sample.py

def func(arg1):
    return arg1

作業ディレクトリの構造は以下です。

.
├── my_sample.py

「mypy」を実行する場合は、コマンドの引数としてファイル名もしくはディレクトリを指定します。

$ mypy my_sample.py

作業ディレクトリに存在するファイルを全てチェックしたい場合は「.(ドット)」を指定します。

$ mypy .

実際にチェックを行いたいファイルを指定して実行すると、何かしらのアラートが表示されます。

$ mypy my_sample.py
Success: no issues found in 1 source file

「my_sample.py」では、型注釈を行っていないので違反等の問題は起こっていないと表示されました。

では型注釈の誤りを起こしてみます。

以下の「func()」関数の戻り値「arg1」は「int型」である必要がありますが、「-> str」の「str型」が戻り値であるとなっています。

# my_sample.py

def func(arg1: int) -> str:
    return arg1

上記のサンプルコードをチェックしてみると、以下のようになります。

$ mypy my_sample.py
my_sample.py:2: error: Incompatible return value type (got "int", expected "str")  [return-value]
Found 1 error in 1 file (checked 1 source file)

「戻り値の型に互換性がありません」とのエラーが表示されましたので、戻り値と戻り値の型注釈の互換性を合わせます。

※幾つもの方法がある中の些細な例です

# my_sample.py

# def func(arg1: str) -> str:
#     return arg1
#
# def func(arg1: int) -> int:
#     return arg1

def func(arg1: int) -> str:
    return f'arg1'

実行すると問題無くクリアします。

$ mypy my_sample.py
Success: no issues found in 1 source file

型注釈をしていない外部ライブラリをインポートしている場合はエラーログが表示されてしまいます。

例えば「requests」モジュールをインストールしてきてインポートします。

# my_sample.py

import requests

上記「my_sample.py」を指定してコマンドを実行します。

$ mypy my_sample.py
my_sample.py:1: error: Library stubs not installed for "requests"  [import]
my_sample.py:1: note: Hint: "python3 -m pip install types-requests"
my_sample.py:1: note: (or run "mypy --install-types" to install all missing stub packages)
my_sample.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
Found 1 error in 1 file (checked 1 source file)

「requestsモジュールにはスタブ(テスト用部品)がありません」とエラーが表示されました。

以下のようなエラーログが表示される場合、そのモジュールにはスタブがあることを認識しているので、pipもしくは「--install-types」オプションのどちらかを使用してインストールすることができます。

my_sample.py:1: note: Hint: "python3 -m pip install types-requests"
my_sample.py:1: note: (or run "mypy --install-types" to install all missing stub packages)

スタブをインストール方法は以下です。

# pipの場合
$ pip install types-requests

# mypyのオプション機能の場合
$ mypy --install-types

外部ライブラリにスタブが用意されておらず、エラーログを回避したい場合はインポート文の後に「# type: ignore」と追記します。

# my_sample.py

import requests # type: ignore

上記「my_sample.py」を指定してコマンドを実行します。

$ mypy my_sample.py
Success: no issues found in 1 source file

型注釈を組み込む事の出来ない外部ライブラリインポート文をスキップすることができました。

インポート文だけでなく、スキップさせたいコードの後に「# type: ignore」を入れることでエラーを回避することができます。

def func(arg1: int) -> str: # type: ignore
    return arg1

ファイル自体をスキップさせたい場合は、ファイル上部に「# mypy: ignore-errors」と入れます。

# mypy: ignore-errors
import requests

def func(arg1: int) -> str:
    return arg1

以下のように型注釈の無い関数や不完全な関数が存在する場合にエラーログを表示させるには、「--disallow-untyped-defs」オプションを付けます。

# my_sample.py

def func1(arg1):
    return arg1

def func2(arg1: str):
    return arg1

def func3(arg1: str) -> None:
    print(arg1)

class Obj(object):
    def __init__(self, name):
        self.name = name
$ mypy --disallow-untyped-defs my_sample.py
my_sample.py:1: error: Function is missing a type annotation  [no-untyped-def]
my_sample.py:4: error: Function is missing a return type annotation  [no-untyped-def]
my_sample.py:11: error: Function is missing a type annotation  [no-untyped-def]

設定ファイルにオプションを定義

mypyの設定ファイルとして、「mypy.ini」もしくは「pyproject.toml」にオプションを定義してデフォルトでの振る舞いを変更することができます。

この記事では「pyproject.toml」を作成して、mypyのオプション機能を定義していきます。

mypyのオプションは「--help」で参照できます。

$ mypy --help
...
...

以下の例は、「--disallow-untyped-defs」オプションを設定ファイル「pyproject.toml」に定義しました。

[tool.mypy]
disallow_untyped_defs = true # 型注釈が無いまたは不完全な関数は禁止を有効

ディレクトリの構造は以下です。

.
├── my_sample.py
├── pyproject.toml

実行する際は、チェックをしたいファイルを指定するだけで、「--disallow-untyped-defs」オプション機能が働きます。

$ mypy my_sample.py

他にも、サードパーティのスタブを自動的にインストールしてチェックを成功させたい場合は以下のように設定ファイルに定義します。

[tool.mypy]
disallow_untyped_defs = true
install_types = true # スタブのインストール
non_interactive = true # スタブのインストールを許可する

他にも様々なオプション機能が用意されているので、公式ドキュメント等を参考にして実装してみてください。

それでは以上となります。

最後までご覧いただきありがとうございました。