はじめに
python3.5 より python にも型ヒントという概念が組み込まれました。
使用するには、 組み込み型を使用するか import typing
を使用し、型のチェックには mypy
を使用します。
mypy について
インストール
まず、 mypy
を使用するためにパッケージをインストールします。
$ pip install mypy
設定ファイル
mypy.ini
を以下のように作成し、そこに設定情報を記入します。
[mypy]
python_version = 3.8
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_untyped_decorators = True
disallow_incomplete_defs = True
check_untyped_defs = True
pretty = True
[mypy-django.*]
ignore_missing_imports = True
設定できるものについては、こちら を参照してください。
いろんな使用方法
組み込み型
組み込み型は、そのまま使用することができます。
num: int = 1
float_num: float = 1.11
is_something: bool = True
text: str = 'hoge'
byte_str: bytes = b'fuga'
arr: list = [0, 1, 2, 3]
Any 型
何の型が変数に入るか分からない場合は、 Any
を使用します。
この型が指定されている場合は、型検査は行われません。
something: Any = Something()
全ての型の基底クラスである object
も同じように使用できますが、object
は、ある値が型安全な方法で任意の型として使えることを示すために使用し、Any
はある値が動的に型付けられることを示すために使用します。
Dict 型
dictionary 型を使用する場合は、 typing.Dict
を使用します。
dictionary: Dict[str, int] = {'hoge': 1, 'fuga': 2}
Tuple 型
タプル型を使いたい場合は、 typing.Tuple
を使用します。
something: Tuple[str, int] = ('hoge', 1)
Set 型
set 型を使用したい場合は、typing.Set
を使用します。
set 型は、重複しないようをのコレクションです。
something: Set[int] = {6, 7}
Sequence 型
シーケンス型を使用したい場合は、typing.Sequence
を使用します。
シーケンス型は、順番(シーケンシャル)に処理するためのデータ構造です。
something: Sequence = [0, 1, 2]
Iterator 型
イテレータ型を使用したい場合は、typing.Iterator
を使用します。
イテレータ型はイテラブルなクラスで __iter__()
を持っている必要があります。
something: Iterator = IterableClass()
Mapping 型
マッピング型を使用したい場合は、typing.Mapping
を使用します。
マッピング型は任意のキー探索を行うためのデータ構造で、Immutable な型です。
something: Mapping[str, int] = {'hoge': 1, 'fuga': 2}
Union 型
ユニオン型を使用したい場合は、typing.Union
を使用します。
ユニオン型では、複数の型を指定することができます。
something_mapping: Mapping[str, int] = {'hoge': 1, 'fuga': 2}
something_union: Union[int, None] = something_mapping.get('hoge') # 1
something_union: Union[int, None] = something_mapping.get('piko') # None
Optional 型
typing.Optional
は、typing.Union
型の None
が必ず選択されているようなイメージです。
something_mapping: Mapping[str, int] = {'hoge': 1, 'fuga': 2}
something_optional: Optional[int] = something_mapping.get('hoge') # 1
something_optional: Optional[int] = something_mapping.get('piko') # None
Callable 型
関数を使用したい場合は、typing.Callable
を使用します。
def something() -> bool:
return True
something_callable: Callable = something
Literal 型
決まった値しか入らないことを保証したいときは、typing.Literal
を使用します。
mode: Literal['r', 'rb', 'w', 'wb'] = 'r' # OK
mode: Literal['r', 'rb', 'w', 'wb'] = 'a' # NG
AnyStr 型
他の種類の文字列を混ぜることなく、任意の種類の文字列を許す関数によって使われるようにしたいときは、typing.AnyStr
を使用します。
def concat(a: AnyStr, b: AnyStr) -> AnyStr:
return a + b
concat(u"foo", u"bar") # OK unicodeが出力されます
concat(b"foo", b"bar") # OK bytesが出力されます
concat(u"foo", b"bar") # NG unicodeとbytesが混ざっているためエラーになります
型のエイリアス
これまでの型を任意の型名で定義することができます。
Vector = List[float]
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]
NewType
異なる型を作るためには typing.NewType
を使用します。
UserId = NewType('UserId', int)
some_id = UserId(524313)
ジェネリックス
Generic 型を使用するには、以下のようにします。
T = TypeVar('T')
# これが Generic 関数
# l にはどんな型でも良い Sequence 型が使える
def first(l: Sequence[T]) -> T:
return l[0]
ユーザ定義のジェネリック型は以下のように使用できます。
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('%s: %s', self.name, message)
logger: LoggedVar[str] = LoggedVar('hoge', 'Test', Logger())
logger.get() # T は、str 型として扱われる
キャスト(typing.cast)
一度、定義した型を変更したいときは、typing.cast
を使ってキャストします。
処理をできる限り速くするため、実行時には意図的に何も検査しません。
a = [4]
b = cast(List[int], a)
c = cast(List[str], a) # [4] が文字列になることはないので、変換したい場合は、自分で変換処理を追加する
定数(typing.Final)
定数を使用する場合は、typing.Final
を使用します。
SOMETHING: Final[str] = 'Something'
おわりに
いかがだったでしょうか?
型を駆使することでより強固なアプリケーションの作成が行えるかと思います。
ただ、型を指定しているからといって、特別処理が早くなることはないので、そこはご注意ください。
実際に動作を見ていないものもあるので、間違っているところなど見つけましたら、お知らせいただけると助かります。
参考