Python TypedDict with Generics

Alex O'Callaghan

PythonのTypedDictとジェネリクス

原文は Alex O'Callaghan により に公開されました。 このブログを購読する

Pythonは3.5で型ヒントをサポートしたが、値にジェネリック型を使ってdictに型付けするのは驚くほど厄介だ。

キーが固定された辞書の型ヒントを定義するのは、typingモジュールのTypedDictクラスを使えば比較的簡単だ。

from typing import TypedDict

class Book(TypedDict):
    id: int
    name: str

book: Book = {"id": 123, "name": "To Kill A Mockingbird"}

次の1行を追加するとしよう。

not_a_book: Book = {"something": "A book doesn't have"}

これをmypyで実行すると、次のようになる。

$ python -m mypy type-dict.py
type-dict.py:9: error: Missing keys ("id", "name") for TypedDict "Book"  [typeddict-item]
type-dict.py:9: error: Extra key "something" for TypedDict "Book"  [typeddict-unknown-key]
Found 2 errors in 1 file (checked 1 source file)

シンプルだ!

typingモジュールはジェネリッククラスをサポートするためにGenericTypeVarも提供している。筆者の場合は、valueというキーを持ち、その型を可変にできるネストしたdictのためのジェネリッククラスを定義したかった。先ほどのbookの例を拡張すると次のようになる。

from typing import TypeVar, Generic, TypedDict

T = TypeVar('T')

class Attribute(TypedDict, Generic[T]):
  value: T

class Book(TypedDict):
  id: Attribute[int]
  name: Attribute[str]

book: Book = {"id": {"value": 123}, "name": {"value": "To Kill A Mockingbird"}}

mypyで型チェックすると完璧に動く!しかし、これを実行してみると……

$ python --version
Python 3.10.12
$ python type-dict-generic.py
Traceback (most recent call last):
  File "/home/aocallaghan/exp/py-type-dict/type-dict-generic.py", line 5, in <module>
    class Attribute(TypedDict, Generic[T]):
  File "/home/aocallaghan/.pyenv/versions/3.10.12/lib/python3.10/typing.py", line 2348, in __new__
    raise TypeError('cannot inherit from both a TypedDict type '
TypeError: cannot inherit from both a TypedDict type and a non-TypedDict base class

Python 3.10では、TypedDict型とそれ以外のものを同時に継承することはできない。しかし、これはPython 3.11で修正済みだ。

$ python --version
Python 3.11.4
$ python type-dict-generic.py

残念ながら、3.11未満のバージョンでこれを実現する簡単な方法はないようだ。一つの方法は、キーを表すのにEnumクラスを使うことだ。

from enum import Enum, auto
from typing import TypeVar, TypedDict

T = TypeVar('T')

class AttributeKeys(Enum):
  value = auto()

class Book(TypedDict):
  id: dict[AttributeKeys, int]
  name: dict[AttributeKeys, str]

book: Book = {"id": {AttributeKeys.value: 123}, "name": {AttributeKeys.value: "To Kill A Mockingbird"}}

not_a_book: Book = {"something": "A book doesn't have"}

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント