1. はじめに
「2021年版Pythonの型ヒントの書き方」の「コレクションの種類の使い分け」を読んだ際「何をどう使い分ければいいんだ...」と絶句したので、簡単にまとめてみました。
2. 型ヒントの注意点
Python3.8までは、以下の書き方がされていました。
from typing import List, Sequence
def func(a: List[int], b: Sequence[float]) -> float:
...
Python3.9からは、typingからコンテナのgeneric versionを呼び出すのがdeprecatedになり、以下の書き方が推奨されています。
from collections.abc import Sequence
def func(a: list[int], b: Sequence[float]) -> float:
...
ちなみにPython3.7以降であれば、__future__.annotationsをimportすれば後者の書き方ができます。
from __future__ import annotations
from collections.abc import Sequence
def func(a: list[int], b: Sequence[float]) -> float:
...
この記事では、前者の書き方をもとに、型ヒントは全てtypingの形に合わせて記載します。
3. 対象のコンテナ
以下の8つのコンテナについて調べました。
tuplelistdequedictCounterdefaultdictsetfrozenset
4. 継承関係の概観図
継承関係をまとめると、以下の図の通りになります。
(PlantUMLで作成しました。)

以下については、図がごちゃごちゃになるため、今回は省きました。
MappingViewItemsViewKeysViewValuesViewOrderedDict
5. 型ヒントの使い分け目安
それぞれについて、mypy --strictがSuccessになる簡単な例を載せています。
5-1. Iterable:forしたい
「forループしたいだけで型は何でもいい」という場合はこれが使えます。中でループしているsumやmaxもこれでいけます。
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()したい
「サイズが欲しいだけで型は何でもいい」という場合はこれが使えます。
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したい
「ある値が入っているか知りたいだけで型は何でもいい」という場合はこれが使えます。
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の出番です。
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です。
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です。
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です。
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です。
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です。
def func(values: AbstractSet[int]) -> AbstractSet[int]:
return values & {1, 2}
func({1, 2})
func(frozenset([1, 2]))
5-10. MutableSet:9 + 代入や削除もしたい
AbstractSetでは追加や削除しようとするとエラーになります。その場合はこれが使えます。
対応するのはsetです。
def func(values: MutableSet[int]) -> MutableSet[int]:
values.pop()
return values
func({1, 2})