mypyやっていく気持ちになったので、社内のコードで実践してみた話を書きます。484)が追加されました。このアノテーションをもとにコード上の型を静的解析してくれるのがmypyです。"int")アノテーションを元に型をチェックして、戻り値の型がintなのにstrを返しているのでエラーになります。484に対応した型アノテーションを付け、mypyを実行し静的チェックをパスすることを指します。この記事を読んだことが大きいです。やっていけそうな気がしました。担当している案件が規模が大きく既存機能の変更頻度も高いものだったので、信頼できる型アノテーションが有ると、修正する際の助けになりそうだなと思いました。Hintingが付けられてる箇所がエラーになり、目につくようになったからというのもあります。スタートラインコードベースはpython3.5で書かれており、"アノテーションは付けたい人が付ける"ぐらいの緩いルールで開発されていました。アノテーションはバッチやらテストやらを除くと7割ぐらいのメソッドには付いているかなといったところです。<dir>を実行してみると3000件近いエラーが出ました。このまま上から順番に潰していく作業は心が折れる可能性があるので、一旦ビジネスロジックの詰まった部分だけに限定し、最小限のオプションのみで実行し修正することにしました。pythonは3.5.2です。<dir>を実行しつつ修正しました。-sオプションはimportしたモジュールのチェックを行わなくするオプションで、これがないとimportしたサードパーティー製のライブラリまでチェックしに行ってしまいます。<dir>で指定したディレクトリ中のモジュールimportはよしなにしてくれます。外部モジュールの関数を呼び出した場合の引数,戻り値の型はAnyになります。--fast-parserオプションはそのうちデフォルトになるオプションで、現行のデフォルトパーサーだと引数のアンパック周りなどでパースエラーになるパターンがあります。python3.6のシンタックスに対応しているのも--fast-parserのみです。やってみて躓いたところなど__init__にアノテーション付け忘れてるクラス中の__init__メソッドの型アノテーションを付けていない箇所がとても多くありました。そもそも__init__にアノテーション付ける意識がなかったです。.PEP484#the-meaning-of-annotationsとのことなので戻り値の型はNoneにしましょう。このパターンは機械的に対応可能なので、適当なスクリプトを書いて対応しました。unusedpythonの静的解析ツールにflake8というものがあり、これを使いCIサーバーでチェックをかけています。flake8のテストケースには未使用のimport文を確認するものがあり、インポートしたものの一度も使っていない場合エラーになります。ここで問題になるのが、関数ではなく変数にアノテーションを付ける場合です。python3.5では変数にアノテーション付ける場合コメントで対応することになっています。(python3.6では新たに変数アノテーションを付ける構文が追加されます。unusedエラーになってしまいます。noqaとコメントを追加してエラー抑制することで対応しました。Noneと書けるので、変数アノテーションによる未使用インポートの問題は出にくくなるかもしれません。循環importモジュール変数やメソッドにアノテーションを付けているとモジュール間で相互importしてしまうパターンがあります。なると実行時にループしてエラーが起きてしまうので対策が必要です。python3.5.2からtyping.TYPE_CHECKINGという変数が使えるようになっており、これはサードパティー製のツールが読むときのみTrueにする変数で、これを使って回避することができます。この変数を使いimport側で対象のモジュールを解析時のみ読み込むようにして回避します。mypy公式のドキュメントにも解決策として載っています。ignoreというコメントを付けるとmypyのテストを抑制することができるのですが、この位置が曲者でした。複数行にまたがる式の中にエラーが有った場合これを抑制するには式の最初の行にコメントを付ける必要があります。ここで付けたコメントはその後の行にも影響が出てしまいます。この行のエラーを無視したい場合、↑にコメントを付ける必要があるhttps://github.com/python/mypy/issues/1032Issueには載っていて、いつか行ごとにignoreできるようになるかもしれません。Typedshedmypyはソース中のアノテーションだけでなく型を定義したスタブファイルを読み込むこともでき、標準のライブラリなどはそちらの方法が使われています。それれらのスタブはtypeshedで管理されていて、mypyに含まれる形でインストールされます。mypyテスト時はビルトイン関数や標準ライブラリの不正な呼び出しを検知してくれるのですが、スタブ自体はpythonのコードから生成されていて細かい部分で食い違っている場合がまだあります。特定バージョンのソースから生成してしていて、アップデートに追従できてないパターンもあるので見つけ次第PRをぶん投げていくのがいいと思います。解決できていない課題SqlAlchemy動的に属性付けていたりバリバリメタクラスを使っているものだったりすると、アノテーションを付けるのが難しいです。argumentを受け取ってsetattrしているのでソースに直接アノテーションを付けるのが難しいです。派生クラスで__init__定義してやればいいのですが、型アノテーション付けるためにわざわざそれぞれのテーブルで__init__を書くかというと躊躇してしまいます。使っている部分はコード上でサブパッケージにまとまっていたので、まるっと対象から外してしまいました。<dir>を実行してもエラーが出なくなるところまでたどり着きました。しようもないパターンというのはほとんどなくて、大体は素直に直していくことができました。Typeshedのバグなどもありましたがignore付けておけばいいのでエラーが消えなくて進めなくなることはありませんでした。幾つかのバグを発見することもできました。テストは書いていたのですが、異状系などテストでカバーできていない場所で見つかりました。修正作業はコードをちゃんと読まないと返している型が判断できないのでそこそこ大変で、毎日少しづつ対応していきました。地道にやっていくしかないでしょう。mypy自体もまだまだ開発中ですが現状でもやってやれないことはないと思います。[]といった何が入るかわからない変数にアノテーションがついているのは助けになっています。明らかに不正な呼び出しをしている箇所をテストする前に発見できるのは嬉しいことです。リファクタに役立つかはまだわかりませんが、複雑すぎる型を返す関数などが分かりやすくなるので書く際に気をつけるようになりそうです。今後やることCIに突っ込むまだCIサーバーでチェックしているわけでないので、メンバーに周知しつつ入れる--strict-optionalをいれるOptionalな型のチェックを厳密にする。experimental-strict-optional-type-and-none-checking--disallow-untyped-defsをいれるタイプアノテーションのついていない関数をエラーにする--disallow-untyped-callsをいれるタイプアノテーションのついていない関数呼び出しをエラーにするという感じで、CIに突っ込みつつ徐々に厳しいオプションにしていけたらいいなあと思います。my(py)!zulipで対応した際のナレッジがつまってる。この記事なんかより読んだほうがいい。Qiitaに翻訳版があります。