Pythonではじまる、型のある世界

Python3.5から[Type Hints](https://www.python.org/dev/peps/pep-0484/)という機能が導入されました。

これは型に関する注釈(型アノテーション)をつけることができる仕様で、具体的には以下のような感じになります([Abstract](https://www.python.org/dev/peps/pep-0484/#abstract)より引用)。

```py3
def greeting(name: str) -> str:
    return 'Hello ' + name
```

アノテーションを実際に行っているのは以下の部分になります。

* `name: str`: 引数`name`が、`str`型であることをアノテート
* `-> str`: 関数`greeting`の返り値の型が`str`であることをアノテート

また、Type Hintsでは[変数宣言における型コメント](https://www.python.org/dev/peps/pep-0484/#type-comments)についても言及されています。

```py3
x = []   # type: List[Employee]
```

こちらは構文ではなく本当にコメントの拡張になりますが、現在既にこうした型に関するコメントを付けているのであれば、上記の記法に乗っ取っておけば将来的に何かしらのツールで型チェックを行えるようになる可能性があります。

これがPythonに導入された、型のある世界・・・になります。

なお、付与されたアノテーションは、実行時にはチェックされません。端的に言えばコメントの延長となります。
そのため強制力はありませんが、実行時に何もしないためパフォーマンスに影響を与えることもありません。

よって原則的には静的解析のための構文になりますが、[`typing.get_type_hints`](https://docs.python.org/3/library/typing.html#typing.get_type_hints)でアノテーション情報が取得できるため、自前で実行時チェックを実装することも可能と思います([typeannotations](https://github.com/ceronman/typeannotations)など)。


## アノテーションの記法

アノテーションの記法について、[typing](https://docs.python.org/3.5/library/typing.html#classes-functions-and-decorators)に沿い紹介していきたいと思います。

### Type aliases

型の別名を定義することが可能です。

[Type aliases](https://docs.python.org/3/library/typing.html#type-aliases)

```py3
Vector = List[float]
```

ただ、個人的な経験からするとこれは混乱を招くこともあるので(クラスなのかエイリアスなのか判別がつかなくなる)、使いどころと名前の付け方については注意が必要と思います。

### Callable

関数の引数/返り値型の定義をまとめたものです。コールバック関数のように関数が引数となる場合、また関数を返すような場合に利用します。

`Callable([引数型], 返り値型)`という記法になります。以下は、関数を引数に取るfeeder/async_queryについて、Callableでアノテーションを行っている例となります。

[Callable](https://docs.python.org/3/library/typing.html#callable)

```py3
from typing import Callable

def feeder(get_next_item: Callable[[], str]) -> None:
    # Body

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    # Body
```

### Generics

いわゆるGenericsも利用することができます。Java等における`List<T>`を以下のように書くことができます。

[Generics](https://docs.python.org/3/library/typing.html#generics)

```py3
from typing import Sequence, TypeVar

T = TypeVar('T')      # Declare type variable

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]
```

また、`TypeVar`ではGenericsとして有効な型を限定することもできます。以下では、AnyStrとして`str`、`bytes`のみ許容しています。

[pep-0484/#generics](https://www.python.org/dev/peps/pep-0484/#generics)

```py3
from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y
```

### User-defined generic types

いわゆるGenerics classで、`MyClass<T>`のようなことをしたい場合、以下のように定義します。

[User-defined generic types](https://docs.python.org/3/library/typing.html#user-defined-generic-types)

```py3
from typing import TypeVar, Generic

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('{}: {}'.format(self.name message))
```

これで、以下のように使えます。

```py3
from typing import Iterable

def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
        var.set(0)
```

### The Any type

全ての`type`は`Any`のサブタイプになります。`Any`はクラスの方の`object`とは異なるので、注意が必要です。

### Union types

幾つかの許容する型をまとめたものになります。`Optional`は、この`Union`の一種になります。

[Union types](https://www.python.org/dev/peps/pep-0484/#union-types)

```py3
from typing import Union

def handle_employee(e: Union[Employee, None]) -> None: ...
```

これは、以下と同義です。

```py3
from typing import Optional

def handle_employee(e: Optional[Employee]) -> None: ...
```

## チェック方法

上記の通り、型アノテーションの記法は型を付けたいシーンの大体を網羅していると思います。
しかし、このままではコメントと同じで、せっかくつけたアノテーションを無視されたとしても気づくすべはありません。

Python標準では型アノテーションをチェックするための公式ツールは提供していません(3.5現在)。そのため、チェックには外部ツールを使う必要があります。

このツールの一種が`mypy`です。

[mypy](https://github.com/JukkaL/mypy)

Python本体の[`typehinting`](https://github.com/ambv/typehinting)のAuthorでもあるJukkaさんが開発されており、型アノテーションにおけるデファクトスタンダードライブラリといっても差し支えないと思います。

統合開発環境におけるチェックでは、PyCharmは一部?サポートをしています。以下は、`str`を引数に取る`greeting`に数値を渡している箇所が警告になっています。

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/16e70c8b-c400-ffb8-f63e-275f5047eeb9.png)

PyCharmは元々[`docstring`やコメントでのアノテーションをサポート](https://www.jetbrains.com/pycharm/help/type-hinting-in-pycharm.html)しており、今後標準に乗っ取ったサポートが追加されるのも期待できると思います。

Visual StudioのPython開発環境(PTVS)は、もともと強力な型推定があるから大丈夫という言ですが、記法の取り込みについては議論されているようです。

[Use type hints in Python 3.x to infer variable types if possible #82](https://github.com/Microsoft/PTVS/issues/82)

### mypyのインストール

ここでは、mypyを使ったチェックを行うため、その導入手順について紹介していきます。

mypyのインストールは`pip`から可能です。

```
pip install mypy
```

~~※`pip install mypy`だと全然別のライブラリが入るので注意が必要~~
[mypy 0.470より、ライブラリ名は`mypy-lang`から`mypy`に変更](http://mypy-lang.blogspot.jp/2017/01/mypy-0470-released.html)されました

当然Python3前提ですが(3.5でなくてもok)、[Python2での対応も行われる予定](http://mypy.readthedocs.org/en/latest/faq.html#all-of-my-code-is-still-in-python-2-what-are-my-options)のようです。
なお、2015/11/2現在では、Python3.5でmypyを利用する場合はmasterからインストールする必要があります。これは、Python3.5で削除された`Undefined`の対応がまだpypiの方には反映されていないためです([issue 639](https://github.com/JukkaL/mypy/issues/639))。

GitHubのmasterからpip installするには、以下のように行ってください。

```
pip install git+https://github.com/JukkaL/mypy.git@master
```


### mypyの利用

公式の[Quick Start](https://github.com/JukkaL/mypy#quick-start)の通りとなりますが、インストール後使えるようになる`mypy`コマンドでチェックを実行可能です。

```
mypy PROGRAM
```

うまくいかない場合は、[Troubleshooting](https://github.com/JukkaL/mypy#troubleshooting)を参考にしてください。基本はここに網羅されています。

実際に実行してみると、実行結果は以下のようになります。

```pycon
>>> mypy examples/basic.py
examples\basic.py:5: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"

```

`-m`オプションで、モジュールを指定することもできます。

```pycon
>>> mypy -m examples
examples\__init__.py:1: note: In module imported here:
examples\basic.py:5: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
```

ここで、`examples/__init__.py`は以下のようになっています。

```
import examples.basic
```

チェックで「`In module imported here:`」とあるように、パッケージを対象とする場合`__init__.py`からたどれないものはチェックされないようです。そのため、モジュール内の全ファイルをチェックしたい場合は注意が必要です。

なお、既存のライブラリなどについてはその中身のソースコードにアノテーションを付けて回るわけにはいかないので、型定義を外部に切り出して定義することも可能です。この型定義のみ記述したファイルはスタブファイルと呼ばれ、その拡張子は`pyi`になります(TypeScriptの`d.ts`のイメージ)。

mypyで準備しておいた`pyi`ファイルを読み込ませる場合は、`MYPYPATH`を設定します。

```
export MYPYPATH=~/work/myproject/stubs
```

なお、`--use-python-path`オプションを使えばPYTHONPATH内のものは参照してくれます。

現在のところ、TypeScriptにおける[`tsd`](http://definitelytyped.org/tsd/)のような型定義管理ツールのようなものはありませんが、[python/typeshed](https://github.com/python/typeshed)への集約が行われているようです(まだ数はないですが)。

この他、詳細については[公式ドキュメント](http://mypy.readthedocs.org/en/latest/basics.html)をご参考ください。


## 導入方針

TypeHintsの導入効果としては、以下のような点が期待できると思います。

* 浅い単体テスト: 関数の誤った利用による実行時エラーを防止するためのテストとして機能させる
 * データベース周りなど、型に厳密性が要求される箇所への適用
 * 日付など、仕様がややこしいもの(UTCなのかどうか、表記方法はなどetc)について、型を明示しチェックしておく
 * 動的呼び出しなどを活用している場合の型明示
* 仕様の明確化: 引数として何が許容され、どういった値が返ってくる可能性があるのか明示する
 * `Optional`を使用することによる、None対応の必要性についての示唆
 * 呼び出す側が事前処理しておくのか、処理側で対応するのかあいまいなケースにおいて(ex.呼び出す前にキャストしておくのかどうか、要素が一つの場合は1要素の配列にしておくかなど)、仕様を明確化することで分担を明示
 * Callback的に利用する関数について、あらかじめその仕様を明確化する(デリゲート)

そして、適用対象については大まかには以下のようなものがあると思います。

* 完全対象: 全処理が対象
* 部分対象: DB周りの処理、特定モジュールなど、一定の基準に基づいて設定された箇所を対象とする

定められた対象に対するアノテーションの導入方法については、以下のパターンが考えられます。

* 完全導入: 適用対象すべてについてアノテーションを付与
* 逐次導入: 事前の設計やコードレビューなどで、つけた方がいいかどうかを逐次判断する
* 自由導入: コメント同様、開発者が必要と感じた際につける

導入に当たっては、まずどんなメリットを期待するのかを明確にする必要があると思います。これがはっきりしていないと、適用の対象と導入方法の基準があいまいになってしまうためです。

この辺りはそもそも期待している効果が本当に得られるのかも含めて試行錯誤中なので、またノウハウが溜まってきたら追記したいと思います。


## 参考資料

* [Python と型ヒント (Type Hints)](http://www.slideshare.net/t2y/python-type-hints-53759060)
* [Python typing モジュールドキュメント](https://docs.python.org/3/library/typing.html)
* PEP 0484
 * [公式](https://www.python.org/dev/peps/pep-0484/)
 * [日本語訳](http://qiita.com/t2y/items/f95f6efe163b29be59af)
* [mypy](https://github.com/JukkaL/mypy)
    * [ドキュメント](http://mypy.readthedocs.org/en/latest/basics.html)
    * [アノテーション例](http://www.mypy-lang.org/examples.html)
* [typehinting](https://github.com/ambv/typehinting)
* [PyCharm support](https://www.jetbrains.com/pycharm/help/type-hinting-in-pycharm.html)
* [Visual Studio Support](https://github.com/Microsoft/PTVS/issues/82)