Python {Article105}

ようこそ「Python」へ...

Pythonの型ヒント(Type Hints)とMyPyでデバッグの効率を上げるには

ここではPythonのType Hints(型ヒント)と「mypy」のツールを使用してプログラムのデバッグ効率を上げる方法を解説します。 Pythonは動的型付き言語なので、C++, Javaのように明示的に「型」を宣言する必要がありません。 なのでデータの型を誤って使ってバグを組み込みやすくなるといったデミリットもあります。 ここではこのデミリットを回避するために、 Pythonで型を明示的に指定する方法とバグを自動的に検出してくれるツール「mypy」の使い方について説明します。

この記事ではMicrosoftのVisual Studio Code(VS Code)を使用していますが、Jupyter NotebookなどのツールでもOKです。 説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。
click image to zoom!
図A:
click image to zoom!
図B:
click image to zoom!
図C:
click image to zoom!
図D:


Pythonの「Type Hints」と「mypy」ツールでバグを自動的に検出するには

  1. Pythonで絵文字を指定した個数だけ表示するプログラムを作る

    Visual Studio Code (VS Code)を起動したら新規フォルダ「mypy」を作成します。 次にフォルダ「mypy」に新規ファイルを作成して行1-5を入力(コピペ)します。

    行1-2では関数「print_emoji()」を定義しています。 この関数では引数で指定した個数分絵文字を表示します。

    行4では絵文字を表示する個数を入力させて変数「number」に保存しています。 ここでは行4に意図的にバグを組み込んでいます。 行5では関数「print_emoji()」を呼び出しています。
    def print_emoji(n):
        print('😃' * n)
    
    number = input('Number: ')  # bug
    print_emoji(number)

    click image to zoom!
    図1
    図1は実行結果です。 行40(図1)の「print('😃' * n)」の計算でTypeErrorが発生しています。 原因は関数の引数「n」に数値(int)ではなく文字(str)が格納されているからです。 引数「n」には「3」ではなく「"3"」が格納されています。


  2. PythonのプログラムにType Hints(型ヒント)を追加して「mypy」ツールでバグがあるか検証してみる

    ここでは前出のプログラムに「Type Hints」を追加して「mypy」ツールでバグがあるか検証してみます。 行4では変数「number」にデータの型「int」を追加しています。 つまり、変数「number」にはint型のデータが格納されるという宣言をしています。
    def print_emoji(n):
        print('😃' * n)
    
    number: int = input('Number: ') # bug 
    print_emoji(number)

    click image to zoom!
    図2-1
    「mypy」ツールをまだインストールしていないときは、VS Codeの「TERMINAL」ウィンドウから「pip install mypy」を入力してインストールします。 ここでは「mypy」が既にインストールされているので「Requirement already satisfied...」が表示されています。


    click image to zoom!
    図2-2
    「mypy」ツールをインストールしたら、VS Codeの「TERMINAL」ウィンドウから「mypy mypy/mypy_1.py」を実行してツールを起動します。 mypyの引数には検証するPythonプログラムのパス名を指定します。 ここでは「mypy」フォルダに格納されている「mypy_1.py」のプログラムを検証しています。

    「mypy」ツールがバグを検出してエラーが表示されています。 行13(図2-2)の変数「number」は、int型のデータを受け取るはずなのに、 str型のデータを受け取ることになるというエラーが表示されています。 つまり、データ型が不一致というエラーを検出しています。 なぜか? 理由はinput()の戻り値は常に「str型」で返されるからです。 このバグを修正するには「int(input('Number: '))」のように戻り値をint型に変換する必要があります。


  3. バグを修正して再度「mypy」ツールでバグがあるか検証して見る【1】

    ここでは行4のバグを修正しています。 さらに行5-6を追加・修正しています。 そして関数「print_emoji()」にも「Type Hints」を追加しています。
    def print_emoji(n: int) -> None:
        print('😃' * n)
    
    number: int = int(input('Number: '))    # fixed bug
    emojis: str = print_emoji(number)       # bug 
    print(emojis)

    click image to zoom!
    図3
    図3は「mypy」ツールの実行結果です。 今度は行14(図3)でエラーを検出しています。 行14(図3)の「print_emoji()」からstr型の戻り値を受け取ることになっているが戻り値は無いというエラーになっています。


  4. バグを修正して再度「mypy」ツールでバグがあるか検証して見る【2】

    ここでは行1-2の関数を「get_emoji()」に変更してバグを修正しています。 この関数からは絵文字が指定した個数分戻り値としてstr型で返されます。 行5では関数「get_emoji()」を呼び出すように修正しています。
    def get_emoji(n: int) -> str:    
        return '😃' * n
    
    number: int = int(input('Number: ')) 
    emojis: str = get_emoji(number)       
    print(emojis)

    click image to zoom!
    図4-1
    図4-1は「mypy」ツールの実行結果です。 「Success:...」が表示されているのでバグが見つからなかったということになります。 「mypy」ツールでの検証が終わったので実際にプログラムを実行してみます。


    click image to zoom!
    図4-2
    図4-2はプログラムの実行結果です。 件数に「3」を入力したら絵文字が3個表示されました。 これでプログラムが正常に動作することが確認できました。