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つのコンテナについて調べました。
tuple
list
deque
dict
Counter
defaultdict
set
frozenset
4. 継承関係の概観図
継承関係をまとめると、以下の図の通りになります。
(PlantUMLで作成しました。)
以下については、図がごちゃごちゃになるため、今回は省きました。
MappingView
ItemsView
KeysView
ValuesView
OrderedDict
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})