Toku's Blog
とあるエンジニアの備忘録

Python3.5のType Hintについて

この記事はPython Advent Calendar 2015の23日目の記事です。 ちゃんとした記事をこのブログに書くのは今回が初です(笑) 早速、やってきまそ。 Type Hintsについて書こうと思ったわけ 最近HaskellやScalaなどの関数型のパラダイムを持つ静的型付け言語を学ぶようになって型推論など面白いなーなど思っていたところに動的型付けのPythonで型についての提案が導入されたので気になった次第です。 動的型付け言語ですからてきと〜にプログラム書いてても通ってしまいます。まぁそこがいいとこでもあるのかもしれませんが、少し大きなライブラリや業務で使うとなるとバグ見つけたりするのに苦労しそうです。 そこでType Hintsがあれば!!!というわけで勉強がてら紹介です。 それと、記事投稿遅れてスイマセンm(_ _)m Type Hintsとは! Type HintsとはPEP0484で提案された静的型解析などを行うための仕様を提案したものです。 ぶっちゃければ、関数の引数・戻り値などに型を指定して型チェックを行うための仕様を提案したもの。 目的は・・・ この提案の目的としては型アノテーションの標準化された構文の提供です。これにより、静的型解析やリファクタリングや実行時の型チェックなどなど、行えるようになります。 まぁ、主には静的型解析がやりたいそうです。 目的じゃないもの この提案が取り込まれた暁にはPythonは静的型付け言語に・・・・!! とはならないわけで。 依然として動的型付け言語ですし、Type Hintsを必須にすることは望んでないようです。 まぁ、自分は取っ付き易いとこがPythonの取り柄の一つだと思っているので、良い考えだと思います。 それでは本題に。 型定義の構文 def func(name: str) -> str: return 'Hello ' + name とまぁ、関数アノテーションを利用して型を指定してあげるだけです。簡単ですね。 型を参照したい場合は__annotations__属性として参照できます。 >>> func.__annotations__ {'name': <class 'str'>, 'return': <class 'str'>} 型の別名 変数に突っ込みましょう。注意するのが、変数名の先頭を大文字にしておくことです。 (ユーザー定義型として扱うので) Name = str def func(name: Name) -> str: return 'Hello ' + name ジェネリクス typingモジュールのTypeVarを使います。 from typing import TypeVar T = TypeVar('T') def p(x: T) -> None: print(x) TypeVarの第1引数は代入する変数名と同じでなければなりません。また、型変数を再定義してもいけません。 この場合の型変数Tは全ての型を受け付けます。こういう型をAny型としています。 (例えば、デフォルトの関数の引数と戻り値はAny型です。何でも受け取るし、何かしら返すから) また、戻り値で指定しているNoneはtype(None)と等価です。 TypeVarは特定の型を指定することができます。 from typing import TypeVar T = TypeVar('T', str, bytes) def p(x: T) -> None: print(x) ちょっと複雑な使い方をこの規約の例から引用してみます。 from typing import TypeVar, Iterable, Tuple T = TypeVar('T', int, float, complex) Vector = Iterable[Tuple[T, T]] def inproduct(v: Vector) -> T: return sum(x*y for x, y in v) この例ではint、float、complex型のペア(タプル)のオブジェクトを持つ、イテレート可能な型Vectorを定義していますね。 inproduct関数の引数として先ほど作成したVector型を受け取りT型(intかfloatかcomplex)を返すように記述されています。 ユーザー定義のクラスでジェネリック ジェネリック型としてユーザー定義のクラスを定義するためにはGeneric基底クラスを使います。 from typing import TypeVar, Generic T = TypeVar('T') S = TypeVar('S') class C(Generic[T, S]): pass 複数指定する場合はカンマで切って指定してください。 多重継承が使えます。 メタクラスはサポートされていません。 呼び出し可能オブジェクト defで作った関数とか、lambdaで作った関数とか、__call__が定義されているクラスのインスタンスとかとか? そういうのを指すときの表現ではCallableを使用します。 Callableは1つ目に引数のリスト、2つ目に戻り値を指定します。 from typing import Callable def async_query(on_success: Callable[[int], None], on_error: Callable[[int, Exception], None]) -> None: pass 引用してるので処理全然書いてませんが。 on_successは引数にint型をとり、戻り値は無し on_errorは引数にint型とException型をとり、戻り値は無し という感じで表現できます。 Callableの1つ目の引数に空のリストを指定することで引数なしを表現することができます。 また、引数リストには省略記号を使用することができます。 Func = Callable[..., str] ここで注意しておくことが、キーワード引数を指定するための仕組みが今のところないことです。 省略記号を利用した場合はキーワード引数を使用することができます。 コレクションの中の型指定 intなどの場合はそのまま指定すればいいですが、辞書とかリストの場合は中のデータの型を指定する必要があります。 Vector = List[float] NameDict = Dict[str, str] NumSet = Set[int] NumFSet = FrozenSet[int] センスない例ですねぇ(笑) 上界の指定 TypeVarのキーワード引数boundを指定することで可能です。 from typing import TypeVar class Comparable(metaclass=ABCMeta): @abstractmethod def __lt__(self, other: Any) -> bool: ...