【Python】いろんな型について調べてみた！（typing）

# はじめに
python3.5 より python にも型ヒントという概念が組み込まれました。
使用するには、 組み込み型を使用するか `import typing` を使用し、型のチェックには `mypy` を使用します。

# mypy について
## インストール
まず、 `mypy` を使用するためにパッケージをインストールします。

```bash
$ pip install mypy
```

## 設定ファイル
`mypy.ini` を以下のように作成し、そこに設定情報を記入します。

```: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
```

設定できるものについては、[こちら](https://mypy.readthedocs.io/en/latest/config_file.html) を参照してください。

# いろんな使用方法
## 組み込み型
組み込み型は、そのまま使用することができます。

```python
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` を使用します。
この型が指定されている場合は、型検査は行われません。

```python
something: Any = Something()
```

全ての型の基底クラスである `object` も同じように使用できますが、`object` は、ある値が型安全な方法で任意の型として使えることを示すために使用し、`Any` はある値が動的に型付けられることを示すために使用します。

## Dict 型
dictionary 型を使用する場合は、 `typing.Dict` を使用します。

```python
dictionary: Dict[str, int] = {'hoge': 1, 'fuga': 2}
```

## Tuple 型
タプル型を使いたい場合は、 `typing.Tuple` を使用します。

```python
something: Tuple[str, int] = ('hoge', 1)
```

## Set 型
set 型を使用したい場合は、`typing.Set` を使用します。
set 型は、重複しないようをのコレクションです。

```python
something: Set[int] = {6, 7}
```

## Sequence 型
シーケンス型を使用したい場合は、`typing.Sequence` を使用します。
シーケンス型は、順番（シーケンシャル）に処理するためのデータ構造です。

```python
something: Sequence = [0, 1, 2]
```

## Iterator 型
イテレータ型を使用したい場合は、`typing.Iterator` を使用します。
イテレータ型はイテラブルなクラスで `__iter__()` を持っている必要があります。

```python
something: Iterator = IterableClass()
```

## Mapping 型
マッピング型を使用したい場合は、`typing.Mapping` を使用します。
マッピング型は任意のキー探索を行うためのデータ構造で、Immutable な型です。

```python
something: Mapping[str, int] = {'hoge': 1, 'fuga': 2}
```

## Union 型
ユニオン型を使用したい場合は、`typing.Union` を使用します。
ユニオン型では、複数の型を指定することができます。

```python
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` が必ず選択されているようなイメージです。

```python
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` を使用します。

```python
def something() -> bool:
    return True

something_callable: Callable = something
```

## Literal 型
決まった値しか入らないことを保証したいときは、`typing.Literal` を使用します。

```python
mode: Literal['r', 'rb', 'w', 'wb'] = 'r' # OK
mode: Literal['r', 'rb', 'w', 'wb'] = 'a' # NG
```

## AnyStr 型
他の種類の文字列を混ぜることなく、任意の種類の文字列を許す関数によって使われるようにしたいときは、`typing.AnyStr` を使用します。

```python
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が混ざっているためエラーになります
```

## 型のエイリアス
これまでの型を任意の型名で定義することができます。

```python
Vector = List[float]
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]
```

## NewType
異なる型を作るためには `typing.NewType` を使用します。

```python
UserId = NewType('UserId', int)
some_id = UserId(524313)
```

## ジェネリックス
Generic 型を使用するには、以下のようにします。

```python
T = TypeVar('T')

# これが Generic 関数
# l にはどんな型でも良い Sequence 型が使える
def first(l: Sequence[T]) -> T:
    return l[0]
```

ユーザ定義のジェネリック型は以下のように使用できます。

```python
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` を使ってキャストします。
処理をできる限り速くするため、実行時には意図的に何も検査しません。

```python
a = [4]
b = cast(List[int], a)
c = cast(List[str], a) # [4] が文字列になることはないので、変換したい場合は、自分で変換処理を追加する
```

## 定数（typing.Final）
定数を使用する場合は、`typing.Final` を使用します。

```python
SOMETHING: Final[str] = 'Something'
```

# おわりに
いかがだったでしょうか？
型を駆使することでより強固なアプリケーションの作成が行えるかと思います。
ただ、型を指定しているからといって、特別処理が早くなることはないので、そこはご注意ください。
実際に動作を見ていないものもあるので、間違っているところなど見つけましたら、お知らせいただけると助かります。

# 参考
https://docs.python.org/ja/3/library/typing.html