[翻訳] Python の静的型、すごい mypy!

本稿は 2016年10月13日 (木) に Tim Abbott 氏によって書かれた記事の翻訳です。

* [Static types in Python, oh my(py)!](http://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/)
* https://news.ycombinator.com/item?id=12703008


# 免責事項/Disclaimer

本稿は **非公式** の翻訳記事です (著者の Tim Abbott 氏に翻訳を公開することの確認は取っています) 。本稿の内容に関して Tim Abbott 氏と Dropbox 社は一切の責任を負いません。

誤訳などありましたら私宛に編集リクエストを送って頂けると助かります。

# 謝辞

* @takada-at に誤訳の指摘をコメントで頂きました
* @shimizukawa から誤訳修正の編集リクエストを頂きました
* @cocoatomo は全体を通して誤訳修正、より分りやすい日本語の表現にしてくれました

私の拙い翻訳をより良い内容に改善してくれたことに感謝します！

# Python の静的型、すごい mypy!

2016年10月13日

— Tim Abbott

過去数年にわたり、静的型チェッカーが PHP ([Hack](http://hacklang.org/)) や JavaScript（[Flow](https://flowtype.org/) と [TypeScript](https://www.typescriptlang.org/)) といった人気のある動的型付き言語で利用できるようになり、幅広く採用されているのを見かけるようになってきています。2年前、[静的型アノテーションのための暫定的な構文](https://www.python.org/dev/peps/pep-0484/) が Python 3 に追加されました。しかし、Python における静的型はまだ広く採用されてはいません。その理由は型アノテーションをチェックするツールである [mypy](http://mypy-lang.org/) が本番環境で使えるほどの品質ではなかったからです。ですが、それもこれまでの話です！

過去1年に渡るおもしろいニュースがあります。Dropbox の1チーム (Python の生みの親である Guido van Rossum も所属！) が Python プログラムに静的型の整合性をもたらす型チェッカーとして十分に機能するように mypy の開発を進めてきました。大規模な Python 2 のコードベースをもつ多くの開発者にとって、さらに一層の興味をひくニュースもあります。mypy は Python 2 のプログラムの型チェックも完全にサポートし、大規模な Python コードベースにも対応できるようになり、mypy を使うことで Python 3 へのアップグレードが大幅に簡略化されます。

mypy にこういったご利益があることを Zulip 開発コミュニティは2016年を通してずっと目の当たりにしてきました。[Zulip](https://zulip.org/) は人気のあるオープンソースのグループチャットアプリケーションです。主要なプラットフォーム全てのアプリを備え、REST API、多くの拡張ツールなどもあります。規模感を理解してもらうために、Zulip は Python 2 で約50,000行、毎月数十人の開発者が数百のコミットをするプロダクトです。2016年を通して、私たちはバックエンドに mypy を用いた静的型のアノテーションを100%付けました (！) 。そして、mypy のおかげで正に Python 3 への切り替えをしようとしているところです。Zulip はいまや完全に静的型を採用した最大規模のオープンソースの Python プロジェクトです。但し、今後も長い間その肩書きを保持できるかについて私は懐疑的なんだけどね :)

本稿では、mypy の仕組み、私たちが mypy を使ってみて感じた利点や苦痛だった点について説明します。そして実務で運用している大規模なコードベースに mypy を採用するための詳細なガイドを共有します (mypy を使い始めて最初の数日に起こる大規模プロジェクトの数十の課題の見つけ方と修正方法を含む！) 。

# mypy の簡単な紹介

Python 3 における mypy/PEP-484 のアノテーション構文の簡潔な例を紹介します。

```py3
def sum_and_stringify(nums: List[int]) -> str:  
    """Adds up the numbers in a list and returns the result as a string."""
    return str(sum(nums))
```

そして、Python 2 と 3 の両方で利用できるコメント構文を使うと、同じコードがどんな見た目になるかを紹介します。

```py
def sum_and_stringify(nums):  
    # type: (List[int]) -> str
    """Adds up the numbers in a list and returns the result as a string."""
    return str(sum(nums))
```

このコメント構文により、mypy は通常の Python 2 のプログラムの型チェックをサポートします。そして mypy でアノテーションを付けたプログラムは任意の Python 実行環境で通常どおり実行されます (mypy のこの優れた特性は JavaScript 型チェッカー Flow にも共通するものです) 。これはとてもすごいことです。つまり Python の実行方法を何も変えずにプロジェクトに対して mypy を採用できるということだからです。

mypy を linter のように実行すると、洗練されたコンパイラースタイルのフォーマットでエラーを出力します。例えば `sum_and_stringify` が float を返すというアノテーションを誤って付けたとしたら、mypy は次のような出力を返します。

```shell-session   
$ mypy /tmp/test.py
/tmp/test.py: note: In function "sum_and_stringify":
/tmp/test.py:6: error: Incompatible return value type: expected builtins.float, got builtins.str
```

アノテーションを付ける方法に興味があるなら [mypy 構文チートシート](http://mypy.readthedocs.io/en/latest/cheat_sheet.html) (簡単な用途) と [PEP-484](https://www.python.org/dev/peps/pep-0484/) (複雑な用途) を確認してみてください。これらは素晴らしいドキュメントです。いますぐ mypy を使ってみたくなったなら `pip3 install mypy-lang` でインストールできます。

mypy でモジュールやその依存パッケージにも完全な型アノテーションが付いたときには、とても強力な整合性チェック機能が得られます。それはコンパイラーが静的型付き言語で得られるものと似ています。mypy は型 "スタブ" (stub: ヘッダーファイルスタイルのモジュールの型定義) のリポジトリである [typeshed](https://github.com/python/typeshed) を使い、Python 標準ライブラリや、requests, six や sqlalchemy といった人気のある数十のライブラリの型情報を提供します。重要なこととして、mypy は漸進的に型を付加するように設計されています。インポートしたものの型情報が利用できない場合、それは単に任意の型と整合性があるものとして扱われます。

# mypy を使う利点

ここでは私たちが mypy を採用して分かった利点を述べます。最も重要なものから順番に取り上げます。

  * **静的型アノテーションを使うと可読性が向上します。**可読性の向上が静的型付けされた Python の最重要な利点なのは確実でしょう。静的型があると、全ての関数に引数と戻り値の型が明記されます。これによって、コードベースを読み、関数が期待している正確な型を把握し、正しく呼び出せるようになるまでの時間が大幅に節約されます。多くの大規模なコードベースでは、開発者が docstring に引数の型以外のちょっとした情報も書いていて、その情報が古くなってしまうことがよくあります。型アノテーションではその正確さや整合性が自動的に検証されるので、docstring よりもはるかに優れています。型アノテーションを付けるのが最も難しかったコードこそが、正に型アノテーションで可読性が最も向上したコードだということになりました。つまり受け渡しされている全てのオブジェクトがいったい何なのか、コード単体からは全く明らかでない場合です。
  * **自信を持ってリファクタリングできます。**何かを壊すことなく Python のコードベースをリファクタリングでは、古いインターフェースを使う全コードを更新したことを確認するためにたくさんのチェックを慎重に手作業で行うことになります。静的型付けされた Python のコードベースでは、型チェッカーでこういった検証を自動的に行える場合が多いです。この利点が [Zulip をリファクタリングするときに素晴らしく役に立っている](https://github.com/zulip/zulip/commit/eb09dd217dbd5d9c214b14a5b28b3a12cf8b7854) ことを既に知っています。
  * **Python 3 へアップグレードしました。**mypy のおかげで Zulip は Python 3 をサポートするようになりました。ずっと前から 2to3 ライブラリを使えば Python 3 をサポートするために必要な修正の大半を自動的に行うことができました。移行の難しいところは Unicode に関する問題です。要は Python 3 は Python 2 よりもバイトの配列と文字列の区別に関してかなり厳格です。mypy の型アノテーションによって、この移行プロジェクトはずっと簡単なものになりました。それはコードベース全体に渡り明示的に値を宣言できて、それらの宣言で整合性が保たれているかをチェックするからです。並外れて包括的で自動化されたテストスイートがある Zulip でさえ、mypy を使うことでそのテストが検出しなかった多くの Python 3 に関する問題が捕捉されました。
  * **mypy によって頻繁にバグが捕捉される。**私たちが mypy を使い始めたとき、Zulip はかなりテストカバレッジの高い成熟した web アプリケーションでした。そのため、型アノテーションを付けたコードベースで恥ずかしくなるようなバグは見つかりませんでした。しかし、mypy は [数十](https://github.com/zulip/zulip/commit/d77c70220c91a47b06695409e178bb006fe996e8) [の](https://github.com/zulip/zulip/commit/fc02ea9f674e44d99eb12528fceaed64a950bbed) [潜在的な](https://github.com/zulip/zulip/commit/e9f39922a09d2aad90de63a349abe408e93edd4d) [バグ](https://github.com/zulip/zulip/commit/7595e4b05fe69a786cf83cad9c8a77f6e47b2f0f) や多数の [入り組んだ](https://github.com/zulip/zulip/commit/c55ac01ae69ef1ca8ce8315ec4b179ec3ebb46a6) コードの [一部](https://github.com/zulip/zulip/commit/eee36618fe297bf306c19a7deb11941b53680272) に [警告を出しました](https://github.com/zulip/zulip/commit/620411c0ea757c9f3f0ae08597e77de84f20ee97) 。mypy がそういった種類の問題をコードベースから除去する支援となってくれたことに私は満足しています。私たちにとってより重要なことは、まだレビューしていない、もしくは一連のテストを実行していないコードを mypy で実行して、バグを捕捉することにより開発時間を節約してくれることです。
  * **静的型によって悪いインターフェースが目立つようになる。**汚いインターフェース (例えば `List` や `Tuple` を返すもの) をもつ関数にアノテーションを付けると本当によく目立ちます。

# 苦痛だった点

mypy を採用した経験の全体像を伝えるには、今日の mypy を用いる苦痛について話すことも重要だと私は思います。

  * **“unused import” linter との相互作用**：現在のところ、`pyflakes` のような linter は Python 2 の型アノテーションスタイルを読み取りません (結局のところ、そのアノテーションはコメントだから！) 。そのため、mypy 向けに必要なモジュールをインポートすると linter はそれは未使用だと報告してきます。未使用のインポートを一掃するのはちょっとしか役に立たないので、私たちはそういった警告をオフにしました。この問題は Python 3 の新しい型構文へ移行することで解決します。Python 3 の型構文なら、 pyflakes のようなツールからもインポートしたものが型アノテーションで使われているようにみえるからです。そのうち、広く使われている Python linter が型コメントのインポートをチェックするためのオプションを追加してくれるのを期待しています。
  * **循環インポート**：ファイル内で引数として使われた型 (しかし、明示的には参照されない) をインポートするために循環インポートを作成する必要がありました。ワークアラウンドの1つとして、例えば (Python 2 のコメント構文の中で) `if False: from x import y` (近いうちにより明示的な `if typing.TYPE_CHECKING` になる) とすることで回避できますが、Python 3 の構文へ移行するときには機能しません。誰かがこの循環インポートを作成せざるを得ない問題に対する優れた解決方法を見つけ出してくれるのを期待しています。その解決方法は単にこの問題を避けるためにコードの推奨構成といったものでも良いかもしれません。

## 苦痛ではなかった点

この節では、(mypy を試す前に) 私が問題になるのではないかと心配していたけれど、mypy の採用後に振り返って大きな問題ではないと思う点について考察します。

  * **トレーニング。**Zulip には現時点で100人をはるかに上回るコントリビューターがいます。そのため、mypy を採用するときに私が最も心配したことは、あまり知られていない新技術を追加してコントリビューターに学習を強いることになり、このプロジェクトに悪影響が出るのではないかという点です。結局のところ、これは大した問題とはなりませんでした。新しいコントリビューターが最初から普通にコードに対してアノテーションを適切に付けてくれるからです。Python プログラマーは Python の型システムを知っています。そして PEP-484 の型構文は型情報を記述するための非常に直感的な手法です。[mypyチートシート](http://mypy.readthedocs.io/en/latest/cheat_sheet.html) は、それさえ見ればよく出てくる全てのケースが調べられ非常に助けになりました。
  * **偽陽性 (誤検知) 。**mypy はプログラムの型に実際にある不整合だけを報告するという考えを軸にして非常によく構築され設計されています。mypy は私が使ったことのある商用の静的解析ツールよりもはるかに低い誤検知率です！そして、エラーのない出力を得るために誤検知を無視する `type: ignore` というよく出来た仕組みがあります。そして遂には、私たちのシステムの動的な部分が多いところ (例えば [REST API リクエストを構文解析するフレームワーク](http://zulip.readthedocs.io/en/latest/writing-views.html#request-variables)) でさえ、幸運なことにうまく型付けできました。
  * **mypy と typeshed のバグ。**Zulip は2016年1月から mypy の利用を開始しました。当時は mypy 自身が mypy を使う唯一のオープンソースプロジェクトでした。つまり私たちは冗談抜きに mypy を使う最初のウェブアプリケーションであり、2番目となる大規模プロジェクトでした。当初、私たちはたくさんの小さなバグに遭遇しました (合計で Zulip 開発者は mypy に最大で50個の課題を報告し、typeshed に 16 個の PR を送りました) 。基本的にこういった問題の全てが数ヶ月前にアップストリームで修正されました (テスト付きで！) 。その後リグレッションが発生することは滅多にありません。そのため、私は類似のプロジェクトが同じぐらいの規模のバグに遭遇するとは思えません。厳密には個々のプロジェクトがどれぐらい多くのバグに遭遇するかは、既に mypy や typeshed を利用している他プロジェクトと同じ Python の隅をつつくかどうかに強く依存します。スーパーアーリーアダプター (訳注：初期採用者のさらに初期) の私たちでさえ、調査や `type: ignore` を付けるワークアラウンド、バグを報告するよりも自分たちのコードに型アノテーションを付けることに多くの時間をかけたように私は実感しています。
  * **パフォーマンス。** mypy は今日私が使ったことがある他の静的解析ツールに比べても本当に速いです。mypy モジュールキャッシュにより、Zulip のコードベース全体をチェックするのは約3秒です。つまりこれは pyflakes (最速の Python linter の1つ) と同じぐらい速いのです。しかし、mypy は (多くの linter とは違い) ファイルを横断する問題を検出するため、ある diff/commit が新たな問題を発生させるかどうかを完全にチェックするのが本当に速い (例えば 100ms 未満) というわけではありません。
  * **コミュニティ。**mypy と typeshed コミュニティは素晴らしく、優れたバグレポート (簡潔且つ簡単な再現方法で大半が5分以内に作成できるもの) を上げることに対して深く信頼を置けます。

# mypy で最初の数日にバグを見つける

この節では、大規模なコードベースで mypy を使うことによる恩恵を受けられるようになるために何をする必要があるかについて詳しく説明します。必要な作業の規模感を把握してもらうため、本節では1月に4日間のハッカソンで行ったことを全て書き出しました (ただ当時の mypy は成熟していなかったため、私は半分の時間を自分が見つけたバグについて適切なバグ報告を書くのにかけました) 。もし mypy の利用を検討しているけれど、その判断を下すためにもっと情報がほしいのなら、本節で考察する手順を全て踏むことを私はお勧めします。その払った労力に対し、十分に見合う価値が得られます。

**mypy チートシートを読んでください。** [mypy チートシート](http://mypy.readthedocs.io/en/latest/cheat_sheet.html) は PEP-484 構文の概要を分かりやすく説明しています。そして型アノテーションを書き始めるときに度々参照することになるでしょう。

**mypy の実行方法を標準化する。**自分たちのコードベースに対して `mypy` を [インストール](https://github.com/zulip/zulip/blob/master/tools/install-mypy) して [実行](https://github.com/zulip/zulip/blob/master/tools/run-mypy) するためのツールを作ってください。そのプロジェクトを使うメンバー全員が同じ方法で型チェッカーを実行できるようにしてください。mypy の実行方法について2つの機能が重要です。

  * どのファイルがチェックされるかを決めるためのサポート (ホワイトリストや除外リストが便利です！)。
  * その時点でのプロジェクトにとって正しいフラグを指定すること。Python 2 のプロジェクトでは、私は `mypy --py2 --silent-imports --fast-parser -i <paths>` で始めるのをお勧めします。[mypy.ini ファイル](http://mypy.readthedocs.io/en/latest/config_file.html) を使って同じことができるはずです。

コードベースに対して **mypy を実行** したときにエラーが出ないようにすること。通常はグローバルの空のデータ構造に対して型アノテーションを追加する必要があります。1月の頃はこの作業に2-3時間かかりました (再現方法を書いたバグ報告の時間を含む) 。おそらく今はずっと少ない作業時間になるでしょう。デフォルトでは mypy はアノテーションが付けられた関数内のみをチェックします。そのため、アノテーションが付いていないコードベースでは、まず mypy コードベース全体を解析できるようにするのです。

**基本的な整合性をチェックする。**mypy の引数に `--check-untyped-defs` を追加してください。そして、そのコードベースで mypy を実行したときにエラーが出ないようにするのです。このオプションは内部の整合性のために mypy がコードベースの全ての `def` をチェックするようにします。つまり mypy は全く型アノテーションが書かれていない状態でもコードベースのバグや誤りをたくさん検出します。

多くの場合、バグやひどいコードを修正したくなるでしょうけれど、`#type: ignore` アノテーションを使ったり、その問題を後回しにするためにファイルを除外したりすることもできます。例えば、私たちは最初に Zulip のテストを全て除外しました。それは型チェックする価値が低いのと、モンキーパッチや怪しい Python スクリプトがたくさんあったからです。Zulip では `--check-untyped-defs` の出力からエラーを一掃するのに私は約2日間の重労働を行い、Zulip のコードベースに約40個の問題を修正するマージしていました。

遭遇した mypy のバグの適切な再現方法を作ったり、typeshed を改善したりするのに私はさらに1日か2日費やしました。今や mypy はもう初期開発状況ではなく mypy のバグに遭遇するのは稀です。しかし、大規模プロジェクトでは、バグに遭遇して typeshed のバグを修正する (単に PR を送るだけです！) ことになるのは予期しておくべきです。

**継続的インテグレーションで mypy を実行する。**対象のコードベースで `mypy --check-untyped-defs` が通るようになったら、CI 環境で `mypy` の型チェッカーを実行して、これまでの進捗の締め括りとすると良いでしょう。

あくまで mypy の型アノテーションはオプションです。上述したセットアップ作業を完了したのなら、自分のペースでコードベースにアノテーションを付けていくことができます。時間が経つにつれ、コードベースのアノテーションを付けた部分で静的型の恩恵を受けるでしょう。残りのコードベースに対して特別に何かする必要も無いのです (漸進的型付けの不思議！) 。次の節では、コードベースに完全にアノテーションが付くようになるための戦略について考察します。

# 大規模コードベースに完全なアノテーションを付けること

この節では、mypy のセットアップが完了したところからコードベース全体に型アノテーションが付くようになるまでに必要な作業に関して考察します。

mypy の素晴らしいところは全ての作業を漸進的にできることです。初期設定後、私たちは実際に2-3ヶ月間何もしませんでした。私たちの Google Summer of Code (GSOC) プロジェクトの1つとして mypy の型アノテーションを提示したときに変化が起きました。このプロジェクトで私たちは Eklavya Sharma という素晴らしい学生と出会いました。Eklavya は Zulip にアノテーションを付ける重労働の大部分を行いました。彼は私たちのツールのアップグレードを行い、コアライブラリにアノテーションを付け、mypy や typeshed のアップストリームへバグ報告や PR といったコントリビュートを行い、私たちが初期の頃に行った全ての間違いを修正しました。驚いたことに、さらに彼はその夏のうちに Zulip が virtualenv を使うように移行し、Zulip を Python 3 へアップグレードしました！

大規模プロジェクトにアノテーションを付ける作業は複数のフェーズに分割できます。

**フェーズ1：コアライブラリにアノテーションを付ける。**戦略的にまず他ファイルの至るところで使われているコアライブラリのコードにアノテーションを付けたくなるでしょう。こういった関数の型アノテーションはコードベースの他のところで使われている型に制約を課すようになります。そのため、これらのファイルから先に作業する場合、誤ったアノテーションの修正にかかる時間が少なくなったり、実在のバグの捕捉が速くなります。さらにこのフェーズは [そのプロジェクトがどのように mypy を使っているか](http://zulip.readthedocs.io/en/latest/mypy.html) についてのドキュメントを書く (そして CI システムの mypy の失敗からドキュメントへリンクを張る) 良い機会でもあります。

**フェーズ2：コードベースの大部分にアノテーションを付ける。**多くのプロジェクトはおそらく何ヶ月もかけて開発者がゆっくりコードベースの様々な部分で作業していきます。それはとても合理的な戦略です。

また集中的に取り組んでコードベースにアノテーションを付けるのもうまくいきます。Zulip がどのようにこの作業を行ったかの話が役に立つでしょう。Eklavya の summer of code 期間の半分ぐらいに差しかかった頃、Zulip にできるだけアノテーションを付けることを目標にして私たちは [PyCon スプリント](https://us.pycon.org/2016/community/sprints/) へ行きました。PyCon スプリントは PyCon における私のお気に入りのイベントです。中心となる PyCon カンファレンスの後に開催される4日間に及ぶ最高の集会です。そこでは数百の開発者が一緒にオープンソースプロジェクトの作業をします。完全に自由に参加でき、オープンソースプロジェクトにコントリビュートする絶好の機会です。

私たちは mypy 開発者の隣にテーブルを確保して、毎日 Zulip の mypy アノテーションプロジェクトに5-10人の開発者を交代で引き込むように調整しました。PyCon スプリントの期間中、Zulip のアノテーションが付けられた割合が17%から85%に上がりました (1日25-30人のエンジニアが作業し、そのエンジニアたちの大半は Zulip と mypy 両方とも未経験でした) 。私たちは進捗状況を把握するために mypy のカバレッジサポートと coveralls.io を使いましたが、大きな紙に描いたプログレスバーの方がおもしろいです。これは最終日の開始時に撮ったものです。

![Zulip mypyカバレッジゴール](https://d2mxuefqeaa7sj.cloudfront.net/s_75D610ACC7DE41FD41A47CCE7C098AB12F65B4E72F59B74EBE6F6100D75C117F_1475462832801_thermometer-pycon-sprint.jpg)

私たちの PyCon での経験は、mypy が新しい開発者に対して取り組みやすいとはっきり証明されたことだと私は思います。私を除いて、アノテーションを追加した全てのコントリビューターは Zulip と mypy 両方とも未経験でした。適切な5分間のデモと優れたドキュメントがあれば、新しいコントリビューターは mypy を触り始めて1時間以内に効率的に作業するということが分かりました。私は他のオープンソースプロジェクトに向けてこの mypy ハッカソンアプローチを自信を持ってお勧めします。この素晴しい方法を採ると、よく知らないプロジェクトでもコントリビューターに有意義な影響を与えられます。

**フェーズ3：100％にする。** 最後の数ファイルにアノテーションを付けるのはそれまでと比べて難しい作業です。その理由は、このフェーズではフェーズ2で行った全ての間違いをデバッグすることになるからです。この作業を行っている期間は、ファイルやディレクトリの100%に至るまで片を付けてしまうのが重要で、そのために `mypy` フラグに `--disallow-untyped-defs` オプション (型アノテーションが付いていない関数を報告する) を追加してリグレッションを避けましょう。

Eklavya は大学の再開前に85%から96%にしてくれました。その後、私たちは100%を達成するために2-3時間の作業を数週間前に行いました。いまや Zulip に追加する新たな Python コードは全て mypy のアノテーションが付いています (ただし数は減るいっぽうですが、いくつかのスクリプト、設定、テストファイルという例外はありました) 。

**フェーズ4：お祝いしてブログ記事を書く！**少なくとも、これは Zulip にとっての次のステップでした :)

全体として、集中的に作業した時間で Zulip に完全にアノテーションが付くことへつながったのは、1週間のハッカソン、GSOC プロジェクト、そして PyCon スプリントの集まりです。当然、これは全く大したことのない労力です。

Zulip はアノテーションが100%付いた状態であるけれども、Zulip の mypy の旅はまだ完了していないことを私は言っておかねばなりません。最終的に私たちは Zulip で使われている最重要ライブラリ (例えば Django) 向けに typeshed へスタブを追加したいと思っています。

# コードにアノテーションを付けるときの推奨事項

実際にアノテーションを付けるときに時間を節約できる可能性のある推奨事項があります。

  * `str` なのか `Text` なのかを間違えずに扱うようにしてください。Bytes なのか str なのかと str なのか unicode なのかのエラーは Python 2 から Python 2+3 へコードベースを移行するときに起きる問題の大半を占めます。コードベースにアノテーションを付けながら移行作業をきちんと行った場合、Python 3 へアップグレードするときに多くの時間を省けるでしょう。コードベースの大半にアノテーションが付いていると Python 3 へのアップグレードがとても迅速に行えることが分かりました。最終的に私たちはコードベースに Python 2 と 3 の両方で `str`, `Text` と `bytes` 間を正しく (そして読みやすく！) キャストするための [数個のヘルパー関数](https://github.com/zulip/zulip/blob/master/zerver/lib/str_utils.py) を追加しました。
  * クラス変数にアノテーションを付けることを忘れないください！最初に Django のモデルファイルにアノテーションを付けたときはこの作業を放置しました。その後、コードベースに入り込んだ多くの誤ったアノテーション (主には Text と bytes の違い) を修正するはめになりました。それは何とも照合されていなかったからです。
  * アノテーションを追加するときに推測してチェックするアプローチはしないように。部分的にアノテーションが付いたコードベースでも mypy は何種類ものアノテーションエラーを検出します。しかし、全てのエラーを検出できるというわけではありません (まだアノテーションを付けていないコードで不整合が発生するかもしれないからです) 。そのため、アノテーションを付ける人は実際にコードを理解し動作を追うべきです。そしてコードレビューも実際のコードと同じように行われるべきです。大きなファイル (または関連している小さなファイルの集まり) ごとに1個のコミットを作るのが適切なコミットの規律になると気付きました。
  * できるだけ正確な型を使う (例えば `Any` を使わない) 。
  * 潜在的な mypy や typeshed のバグをワークアラウンドで回避するために `type: ignore` を使うときは、次のスタイルを使って、元となる GitHub のissueを記録するのを推奨しています。

```py
bad_code # type: ignore # https://github.com/python/typeshed/issues/372
```

この方式だと、将来 `type: ignore` で回避した課題がアップストリームで修正されているかどうかを容易に確認できるようになります。あるファイルに多くの `type: ignore` アノテーションを付ける必要が出てきた場合は、そのファイルを除外リスト (私たちの `run-mypy` ラッパーの機能) に追加して後回しにしても構いません。

# 結論

全体を通して、 mypy (と PEP-484 型システム) を使ってみた経験は素晴らしいものでした。そして mypy を採用することは Zulip プロジェクトの大きな進歩となったと私たちは感じています。mypy は可読性を向上させ、実行することなくバグを捕捉し、誤検知は非常に少なく、大きな欠点はありません。mypy を大規模なコードベースで活用することは、私たちのプロジェクトでは相対的小さな投資で済みました。さらにコードベースにアノテーションを付けることは Python 3 への移行を簡単に行えるという副次的な恩恵をもたらしました。

もし大規模な Python コードベースがあってコードベースをより良くしたいのなら、1週間の時間を確保して mypy を使い始めるべきです！

最後に大規模コードベースにおける Python の静的型がどういうものかについて興味をもったなら [GitHub 上の Zulip サーバープロジェクト](https://github.com/zulip/zulip/) をチェックアウトしてください。私たちは新しいコントリビューターを歓迎します！

このブログ記事へのフィードバックをいただいた Guido van Rossum, Alya Abbott, Steve Howell, Jason Chen, Eklavya Sharma, Anurag Goel そして Joshua Simmons に深く感謝します。

#### [Tim Abbott](http://blog.zulip.org/author/tabbott/)

Tim Abbott は Zulip オープンソースプロジェクトのリード開発者です。彼は (Dropbox に買収される以前) Ksplice 社、後の Zulip 社の CTO でした。

San Francisco <https://zulip.org>