2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KLab EngineerAdvent Calendar 2024

Day 3

Python TypedDict 雑記

Last updated at Posted at 2024-12-02

Python の typing.TypedDict の雑記。どういったものか、どう使うものなのかのメモ。

TypedDict の概要

TypedDict とは「キーが固定されていて、それに対応した値の型が制限されている dict」を宣言するためのものです。呼び出したときは dict と同じくただ dict インスタンスを作るという挙動をします。型チェッカーにのみ影響をおよぼします。

たとえば、次のコード a.py を mypy で検査すると 9 行目の変数 b への辞書のバインドが想定外であろうということを検知します。いっぽう、 Python スクリプトとしてだけみるとおかしなところはないため実行が可能です。そして Point2D と dict を比較する assert 文も無事に通過します。

a.py
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__  # 変更可能

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?