• ITに強い編集プロダクション・リブロワークスのWebサイト

    以前のブログで、プログラミング言語の動的型付けについて紹介しました。動的型付けは、型を記載する必要がありません。そのため、プログラミング初学者にとって敷居が低いというメリットがあります。ただし、型の記載がないがゆえに、安全性が低いという弱点もあります。そこで今回は、動的型付け言語であるPythonで、型チェックを行う方法を試してみようと思います。なお型チェックには、「mypy」というライブラリを使います。

    mypyとは

    mypyとは、Pythonのコードを型チェックできるライブラリです。ここでいう「型チェック」とは、指定した型通りの値が変数に代入されているか、引数が渡されているか、戻り値になっているか、などを静的に検査することです。mypyはサードパーティ製パッケージなので、別途インストールが必要です。Python3.6以降がインストールされている環境で、以下のコマンドを打つとインストールできます。

    pip install mypy

    mypyは、Pythonの型ヒントという記法で書かれた情報をもとに、プログラムの実行前に型チェックを行います。つまり、型ヒントがない場合は、チェックがほぼ行われません。

    型ヒントとは

    mypyを実際使う前に、型ヒントが何かを理解する必要があります。型ヒントは、変数や引数、戻り値における型を記載できる記法のことで、Python 3.5で導入されました。たとえば、変数numをint型と想定する場合は、型ヒントを使うと以下のように書けます。

    num:int

    変数の宣言と代入を同時に行う場合は、以下のように記載します。

    num:int = 100

    そして、関数の引数と戻り値で型を記載する場合は、以下のようになります。

    def cal_total(num: int) -> int:
        return 200 * num

    上記を見るとわかるように、引数の型を記載するには、変数の宣言と同じように記載します。また、戻り値の型は、関数やメソッドの()の後に、「->」で記載します。

    実際に使う場合は、リストや辞書の型を指定したいということが、よくあると思います(Javaなどではお馴染みの機能ですね)。リストの型を指定するには、以下のように書きます。

    num_list: list[int] = [2, 5, 10]

    これは、Python 3.9以降の書き方です。3.8、3.7では、以下のように書きます。

    from __future__ import annotations
    
    
    num_list: list[int] = [2, 5, 10]

    辞書の型を記載するには、以下のように書きます。

    price_dict: dict[str, int] = {"みかん": 100, "りんご": 250, "いちご": 1000}

    型ヒントを使うと、型の記述はできますが、Pythonを実行する際に、この指定に則ったチェックが行われるわけではありません。この型ヒントを用いた「型チェック」は、IDEやチェックツールで行います。型チェックを行える代表的なツールが、mypyです。

    mypyを試してみる

    では、実際にmypyを試してみます。mypyは、CLI上で以下のコマンドを打つと実行できます。

    mypy プログラムのファイル名

    以下のコードをmypyでチェックしてみます。

    num: int
    num = 'Hello'

    チェック結果は以下の通りです。

    sample.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int")
    Found 1 error in 1 file (checked 1 source file)

    変数numにint型の指定があるにもかかわらず、文字列を代入したため、エラーメッセージが表示されました。

    次のコードもチェックしてみましょう。

    def cal_total(num: int) -> int:
        return 200 * num
    
    
    print(cal_total(2))
    print(cal_total('みかん'))

    チェック結果は以下の通りです。

    sample.py:6: error: Argument 1 to "cal_total" has incompatible type "str"; expected "int"
    Found 1 error in 1 file (checked 1 source file)

    cal_total関数の引数numには、int型の指定があります。にもかかわらず文字列を渡しているため、 エラーメッセージが表示されました。

    mypyは、設定ファイルでエラーにする対象をカスタマイズすることもできます。 コードのファイルと同じ階層に、mypy.iniというファイルを配置します。たとえば、「disallow_untyped_defs = True」を指定すると、型ヒントがない関数の定義ができなくなります。

    [mypy]
    python_version = 3.10
    disallow_untyped_defs = True
    def cal_total(num):
        return 200 * num

    チェック結果は以下の通りです。

    sample.py:1: error: Function is missing a type annotation
    Found 1 error in 1 file (checked 1 source file)

    設定をしたことで、注釈(型ヒント)がない関数は定義できない、というエラーが出ました。

    まとめ

    型を記載しないのは手軽ですし、コードも短くなります。しかし、型を記載することで、コードの安全性は高くなりますし、コードの意図を、コードを読む人に伝えやすくなります。また、個人的にはもともとJavaを書いていたこともあって、型が一目見ただけでわかるのは、とても安心感があります。なので、型ヒントやmypyのような型チェックツールは、複数人で共有するコードであればあるほど、有用な手段だと思います。

    また、mypyは警告を発生させますが、プログラムの実行ができなくなる、といったものでもないので、型ヒントを記載するルールになっているプロジェクトでは、導入しやすいように感じました。型ヒントに関しては、Python 3.5で導入された後、バージョンごとに仕様がすこしずつ変わった経緯もあるので、今後の動向にも注目していきたいと思います。