Python の typing.TypedDict の雑記。どういったものか、どう使うものなのかのメモ。
TypedDict の概要
TypedDict とは「キーが固定されていて、それに対応した値の型が制限されている dict」を宣言するためのものです。呼び出したときは dict と同じくただ dict インスタンスを作るという挙動をします。型チェッカーにのみ影響をおよぼします。
たとえば、次のコード a.py を mypy で検査すると 9 行目の変数 b への辞書のバインドが想定外であろうということを検知します。いっぽう、 Python スクリプトとしてだけみるとおかしなところはないため実行が可能です。そして Point2D と dict を比較する assert 文も無事に通過します。
from typing import TypedDict
class Point2D(TypedDict):
x: int
y: int
label: str
a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
> mypy a.py
a.py:9: error: Missing keys ("x", "y") for TypedDict "Point2D" [typeddict-item]
a.py:9: error: Extra key "z" for TypedDict "Point2D" [typeddict-unknown-key]
Found 2 errors in 1 file (checked 1 source file)
> python a.py
>
TypedDict の詳細
NotRequired
デフォルトではキーの過不足は型チェッカー上でのエラーとなります。しかし、あってもなくてもよいキーを定義できると便利なことがあるものまた事実です。これを設定するには typing.NotRequired をもちいます。
from typing import TypedDict, NotRequired
class Language(TypedDict):
name: str
year: NotRequired[int]
Language(name='C', year=1972) # OK
Language(name='B') # OK
c: Language = {'name': 'C', 'year': 1972} # OK
b: Language = {'name': 'B'} # OK
total と Required
非必須のキーのほうが多く、いちいち typing.NotRequired を設定するのが手間である場合、必須・非必須のデフォルトを反転させることができます。キーワード引数 total を用います。このとき必須なキーを定義するのには typing.Required を用います。この設定が行われている場合、 __total__ クラス属性が False となります。
from typing import TypedDict, Required
class Language(TypedDict, total=False):
name: Required[str]
year: int
assert Language.__total__ is False
Language(name='C', year=1972) # OK
Language(name='B') # OK
c: Language = {'name': 'C', 'year': 1972} # OK
b: Language = {'name': 'B'} # OK
ReadOnly
dict インスタンス作成後に値が変更されることを想定していない、つまり読み込み専用のキーであるという設定をすることができます。 typing.ReadOnly を用います。
from typing import TypedDict, NotRequired, ReadOnly
class Language(TypedDict):
name: ReadOnly[str]
year: ReadOnly[NotRequired[int]]
b: Language = {'name': 'B'}
b['name'] = 'C' # Fails type check
Introspection
定義した TypedDict の設定がどのようになっているかを確認することができます。どのキーが必須なのか非必須なのか、読み込み専用なのか変更可能なのかを知ることができるようになっています。それらキーの名前は __required_keys__, __optional_keys__, __readonly_keys__, __mutable_keys__ に frozenset として収まっています。
# それぞれ、 frozenset[str] が得られる。
Language.__required_keys__ # 必須
Language.__optional_keys__ # 非必須
Language.__readonly_keys__ # 読み込み専用
Language.__mutable_keys__ # 変更可能
参考