コンテナの型ヒントのつけ方まとめ

# 1. はじめに
「[2021年版Pythonの型ヒントの書き方](https://future-architect.github.io/articles/20201223/)」の「[コレクションの種類の使い分け](https://future-architect.github.io/articles/20201223/#%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E7%A8%AE%E9%A1%9E%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91)」を読んだ際「何をどう使い分ければいいんだ...」と絶句したので、簡単にまとめてみました。

# 2. 型ヒントの注意点
Python3.8までは、以下の書き方がされていました。

```python:Python3.8以前
from typing import List, Sequence

def func(a: List[int], b: Sequence[float]) -> float:
    ...
```

Python3.9からは、`typing`からコンテナのgeneric versionを呼び出すのがdeprecatedになり、以下の書き方が推奨されています。

```python:Python3.9以降
from collections.abc import Sequence

def func(a: list[int], b: Sequence[float]) -> float:
    ...
```

ちなみにPython3.7以降であれば、`__future__.annotations`をimportすれば後者の書き方ができます。

```python:Python3.7-3.8
from __future__ import annotations
from collections.abc import Sequence

def func(a: list[int], b: Sequence[float]) -> float:
    ...
```

この記事では、前者の書き方をもとに、型ヒントは全て`typing`の形に合わせて記載します。

# 3. 対象のコンテナ
以下の8つのコンテナについて調べました。

- `tuple`
- `list`
- `deque`
- `dict`
- `Counter`
- `defaultdict`
- `set`
- `frozenset`

# 4. 継承関係の概観図
継承関係をまとめると、以下の図の通りになります。
（PlantUMLで作成しました。）
![collections.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1432808/9d251d05-115b-2ac2-abe8-924c5801d2aa.png)

以下については、図がごちゃごちゃになるため、今回は省きました。

- `MappingView`
- `ItemsView`
- `KeysView`
- `ValuesView`
- `OrderedDict`

# 5. 型ヒントの使い分け目安
それぞれについて、`mypy --strict`がSuccessになる簡単な例を載せています。

## 5-1. `Iterable`：`for`したい
「forループしたいだけで型は何でもいい」という場合はこれが使えます。中でループしている`sum`や`max`もこれでいけます。

```python
def func(values: Iterable[int]) -> int:
    return sum(values)

func((1, 2))
func([1, 2])
func(deque([1, 2]))
func({1: 'a', 2: 'b'})
func(Counter([1, 1, 2]))
func(defaultdict(str, {1: 'a', 2: 'b'}))
func({1, 2})
func(frozenset([1, 2]))
```

## 5-2. `Sized`：`len()`したい
「サイズが欲しいだけで型は何でもいい」という場合はこれが使えます。

```python
def func(values: Sized) -> int:
    return len(values)

func((1, 2))
func([1, 2])
func(deque([1, 2]))
func({1: 'a', 2: 'b'})
func(Counter([1, 1, 2]))
func(defaultdict(str, {1: 'a', 2: 'b'}))
func({1, 2})
func(frozenset([1, 2]))
```

## 5-3. `Container`：`in`したい
「ある値が入っているか知りたいだけで型は何でもいい」という場合はこれが使えます。

```python
def func(values: Container[int]) -> bool:
    return 1 in values

func((1, 2))
func([1, 2])
func(deque([1, 2]))
func({1: 'a', 2: 'b'})
func(Counter([1, 1, 2]))
func(defaultdict(str, {1: 'a', 2: 'b'}))
func({1, 2})
func(frozenset([1, 2]))
```

## 5-4. `Collection`：1から3まで全部したい
例えば下のように平均を求めてみると、`Iterable`か`Sized`だけではエラーが出てしまいます。ここで`Collection`の出番です。

```python
def func(values: Collection[int]) -> float:
    return sum(values) / len(values)

func((1, 2))
func([1, 2])
func(deque([1, 2]))
func({1: 'a', 2: 'b'})
func(Counter([1, 1, 2]))
func(defaultdict(str, {1: 'a', 2: 'b'}))
func({1, 2})
func(frozenset([1, 2]))
```

## 5-5. `Sequence`：`object[index]`したい
添え字で中身を取得したい場合はこれが使えます。
ここからはコンテナが限定されて、対応するのは`tuple`, `list`, `deque`です。

```python
def func(values: Sequence[int]) -> Sequence[int]:
    return values[:2]

func((1, 2))
func([1, 2])
func(deque([1, 2]))
```

## 5-6. `MutableSequence`：5 + 代入や削除もしたい
`Sequence`では取得はできても、代入や削除しようとするとエラーになります。その場合はこれが使えます。
対応するのは`list`, `deque`です。

```python
def func(values: MutableSequence[int]) -> MutableSequence[int]:
    values[0] = 3
    return values[:2]

func([1, 2])
func(deque([1, 2]))
```

## 5-7. `Mapping`：辞書だけど取り出すだけでいい
辞書っぽいコンテナから中身を取得したい場合はこれが使えます。
対応するのは`dict`, `Counter`, `defaultdict`です。

```python
def func(mapping: Mapping[str, int]) -> int:
    return mapping['a']

func({'a': 1, 'b': 2})
func(Counter(['a', 'a', 'b']))
func(defaultdict(int, {'a': 1, 'b': 2}))
```

## 5-8. `MutableMapping`：7 + 代入や削除もしたい
`Mapping`では取得はできても、代入や削除しようとするとエラーになります。その場合はこれが使えます。
対応するのは`dict`, `Counter`, `defaultdict`です。

```python
def func(mapping: MutableMapping[str, int]) -> int:
    mapping['a'] = 3
    return mapping['a']

func({'a': 1, 'b': 2})
func(Counter(['a', 'a', 'b']))
func(defaultdict(int, {'a': 1, 'b': 2}))
```

## 5-9. `AbstractSet`：セットだけど何もしない
セットを、特に変更を加えずに使う場合はこれが使えます。
対応するのは`set`, `frozenset`です。

```python
def func(values: AbstractSet[int]) -> AbstractSet[int]:
    return values & {1, 2}

func({1, 2})
func(frozenset([1, 2]))
```

## 5-10. `MutableSet`：9 + 代入や削除もしたい
`AbstractSet`では追加や削除しようとするとエラーになります。その場合はこれが使えます。
対応するのは`set`です。

```python
def func(values: MutableSet[int]) -> MutableSet[int]:
    values.pop()
    return values

func({1, 2})
```

# 6. 参考
- [typing.py](https://github.com/python/cpython/blob/main/Lib/typing.py)
- [_collections_abc.py](https://github.com/python/cpython/blob/main/Lib/_collections_abc.py)
- [collections.abc — Abstract Base Classes for Containers](https://docs.python.org/3/library/collections.abc.html)