mypy 設定ファイルの読み合わせと修正を実施しました

こんにちは MLエンジニアとMLOpsエンジニアを兼任している押条です。

CADDi の機械学習プロジェクトでは Python がよく使われています。 本番環境にリリースするコードのlinter や formatter、test に関しては、 CI で自動的に適用し運用できていました。 一方、型注釈については開発担当者に一任されており、プロジェクトごとにあるものとないものと混在していました。 採用を強化してチームを拡大していこうという動きもあり、開発に携わる人が増える見込みがあります。 そのような背景から、型注釈による既存コードの可読性の向上の重要性が増してきました。

そこで現行の mypy の設定の読み合わせと mypy 設定の修正を実施しました。 その時の議論の内容と修正後の設定をご紹介しようと思います。

目次

  • 現行ルールがどのようなものか認識合わせする
  • 現在のプロダクションコードを参照しながらルールのスコープを決める
  • 修正後の設定ファイルの紹介
  • おわりに

現行ルールがどのようなものか認識合わせする

まずは現在使用している mypy の設定ファイルの読み合わせをしました。 この設定は MLOps チームのテックリードが厳しめに設定してくれたものでした。 ChatGPT に入力して、説明してもらったのが下記です。

この設定は、`mypy`に関する多くのオプションを含んでいます。各オプションについて簡単に説明し、必要に応じて改善の提案を行います。

1. `allow_redefinition = False`: 同じスコープ内での変数の再定義を禁止します。通常、このような再定義はエラーの原因となります。

2. `allow_untyped_globals = False`: 型注釈がないグローバル変数の使用を禁止します。型情報が不足している場合、予期しないエラーが生じる可能性があります。

3. `check_untyped_defs = True`: 型注釈がない関数やメソッドに対して型チェックを行います。

4. `color_output = True`: エラーメッセージに色を付けて表示します。

5. `disallow_incomplete_defs = True`: 型注釈の一部が欠けている関数やメソッドの定義を禁止します。

6. `disallow_untyped_defs = True`: 型注釈がない関数やメソッドの定義を禁止します。

7. `disallow_untyped_calls = True`: 型注釈がない関数やメソッドの呼び出しを禁止します。

8. `disallow_untyped_decorators = True`: 型注釈がないデコレータの使用を禁止します。

9. `error_summary = True`: エラーメッセージの概要を表示します。

10. `ignore_missing_imports = True`: 型情報がないモジュールのインポートを無視します。多くのサードパーティライブラリは型情報を持っていないため、このオプションが有効になっています。

11. `implicit_reexport = False`: 型注釈がない関数やメソッドの再エクスポートを禁止します。

12. `no_implicit_optional = True`: Optionalの型を明示的に宣言する必要があります。

13. `pretty = True`: エラーメッセージを見やすくフォーマットします。

14. `show_column_numbers = True`: エラーの発生した列番号を表示します。

15. `show_error_codes = True`: エラーコードを表示します。これは、特定のエラーを無視するための設定を行う際に便利です。

16. `show_error_context = True`: エラーメッセージのコンテキストを表示します。

17. `show_traceback = True`: mypyがクラッシュした場合にトレースバックを表示します。

18. `strict = True`: すべてのstrictモード関連のオプションを有効にします。このオプションが有効になっている場合、以下のオプションも自動的に有効になります。

19. `strict_equality = True`: 異なる型同士の等価性の比較を禁止します。

20. `strict_optional = True`: Noneと他の型の混在を許容しないようにします。

21. `warn_no_return = True`: 戻り値がない関数やメソッドでのreturn文の省略に警告します。

22. `warn_redundant_casts = True`: 不要な型変換に警告します。

23. `warn_return_any = True`: `Any`型を返す関数やメソッドに警告します。

24. `warn_unreachable = True`: 到達不能なコードに警告します。

25. `warn_unused_configs = True`: 使用されていないconfigオプションに警告します。

26. `warn_unused_ignores = True`: 使用されていない`# type: ignore`コメントに警告します。

**改善の提案**:

1. `ignore_missing_imports = True`: これはグローバルに適用されるため、可能ならば特定のライブラリにのみこのオプションを適用することをおすすめします。全体的な型の厳格性が低下する可能性があります。

2. `strict = True`: このオプションは多くの他のstrictモード関連のオプションを有効にするため、個別に指定したオプション(例: `strict_equality`, `strict_optional` など)は削除しても良いです。

全体的に、この設定は非常に厳格な型チェックを行うように設定されており、コードの品質を向上させるのに役立ちます。ただし、非常に厳格であるため、既存のコードベースでこれらの設定を有効にすると、多くのエラーが発生する可能性があります。新しいプロジェクトや、既存のプロジェクトでの大規模なリファクタリングを行う際には、これらの設定を検討すると良いでしょう。
warn_redundant_casts = True
warn_return_any = True
warn_unreachable = True
warn_unused_configs = True
warn_unused_ignores = True

各オプションの役割が何となくわかるようになりました。 prefix が show のオプションや warning のオプションのほとんどについては、True で問題なさそうです。 それ以外のオプションについて、1行ずつ確認して必要があれば議論も実施しました。

現在のプロダクションコードを参照しながらルールのスコープを決める

オプションの確認を行うにあたって、現在のプロダクションコードに対して無理なく適用できそうかどうかを意識して取り組みました。

それでは順番に見ていきましょう。

  1. allow_redefinition

こちらは変数の再定義に関するオプションです。

画像解析の Deep Learning モデル開発の現場では、このようなコードが頻繁に見られると思います。

image : PIL.Image = PIL.Image.open('path_to_image.png')
image : np.array = np.array(image)
image : torch.Tensor = torch.from_array(image)

再定義が許容されない場合以下のようなコードになりそうです。

image : PIL.Image = PIL.Image.open('path_to_image.png')
image_arr : np.array = np.array(image)
image_tensor : torch.Tensor = torch.from_array(image_arr)

リーダブルコードにも記述があるように変数には適切な名前をつけるのが望ましいですが、幾分冗長に思えます。

後述する設定項目によって、私たちはできるだけ型注釈を省略せずコードを書こうとしています。 その場合、変数に型情報が紐づいているため、変数名に型の情報を持たせなくて良くなります。

redefinition を許可しても可読性が損なわれることはないと判断し、allow_redefinition = True としました。

  1. allow_untyped_globals

こちらは 型注釈のないグローバル変数を許容するかどうかに関するオプションです。

そもそも グローバル変数は使わない方が良いが、定数の場合 Final 型 を使用して意図しない変更を検知するようにしていこうという議論になりました。結論は False です。

  1. check_untyped_defs

型注釈がない関数やメソッドに対して型チェックを行うかどうかに関するオプションです。 型チェックを自動化するために型チェッカを利用しているのでこちらは True です。

  1. disallow_incomplete_defs

我々が記述するコードについては、型注釈を欠損なく書いていきたいです。こちらも True です。

  1. disallow_untyped_defs

型注釈がない関数やメソッドの定義に関するオプションです。こちらも True です。

  1. disallow_untyped_calls

型注釈がない関数やメソッドの呼び出しに関するオプションです。 前述の disallow_untyped_defs = True、disallow_incomplete_defs = True により我々が定義したコードには型注釈がついている状態になっているはずです。 call まで True にしてしまうとサードパーティライブラリにもルールが適用されてしまいます。 厳しすぎるため、我々のルールでは False にしました。

  1. disallow_untyped_decorators

型注釈がないデコレータに関するオプションです。 True にしてしまうとサードパーティライブラリにもルールが適用されてしまいます。 サードパーティライブラリにはデコレータに型がついていない場合があります。 厳しすぎるため、我々のルールでは False にしました。

  1. ignore_missing_imports = True: 型情報がないモジュールのインポートを無視します。多くのサードパーティライブラリは型情報を持っていないため、このオプションが有効になっています。

True で良さそうです。

  1. implicit_reexport = False: 型注釈がない関数やメソッドの再エクスポートを禁止します。

我々は型注釈のない関数を定義しないので False です。

  1. no_implicit_optional = True: Optionalの型を明示的に宣言する必要があります。

True で良さそうです。

  1. strict = True: すべてのstrictモード関連のオプションを有効にします。このオプションが有効になっている場合、以下のオプションも自動的に有効になります。

strict_equality と strict_optional を明示的に True にしているため strict オプションは削除で良さそうです。

  1. warn_no_return

戻り値がない関数やメソッドでのreturn文の省略に対する警告についてのオプションです。

↓ このようなコードはエラーにしてほしいため True に設定します。

def example_function(value: int) -> int:
    if value > 0:
        return value
    # ここに戻り値の記述がない
  1. warn_unused_ignores

使用されていない# type: ignoreコメントへの警告に関するオプションです。 基本的に True にすべきですが、我々が採用しているビルドシステムとの相性が悪く、一時的に False としています。

修正後の設定ファイルの紹介

議論の結果出来上がった設定ファイルは次のようなものになりました。

[mypy]
allow_redefinition = True
allow_untyped_globals = False
check_untyped_defs = True
color_output = True
disallow_incomplete_defs = True
disallow_untyped_calls = False
disallow_untyped_decorators = False
disallow_untyped_defs = True
error_summary = True
ignore_missing_imports = True
implicit_reexport = True
namespace_packages = True
no_implicit_optional = True
pretty = True
show_column_numbers = True
show_error_codes = True
show_error_context = True
show_traceback = True
strict = True
warn_no_return = True
warn_redundant_casts = True
warn_return_any = True
warn_unreachable = True
warn_unused_configs = True
warn_unused_ignores = False

おわりに

チームで mypy 設定ファイルの読み合わせを実施しました。 今回ご紹介した取り組み以外にも、python typing や pydantic などの型注釈に関するドキュメントの読み合わせなど、チーム全体で知識を底上げしたり知見を共有したりする取り組みを進めています。このような取り組みによって、より安全で高速な開発を推進していきます。

我々と一緒に開発を推進してくださるメンバーを募集しています。興味のある方、是非気軽にご連絡ください!

エンジニア向けサイト

カジュアル面談

機械学習エンジニアの求人