TypedDictでpythonのtype hintでdictのkeyとvalueに厳格に型をつける
Kazuki Moriyama (森山 和樹)
はじめに
この記事では、Pythonの型ヒントを使用してdict型のkeyとvalueの対応に厳格に型をつける方法を紹介します。
動作環境
- Pythonの型チェックには
pyright
のstrict modeを使用します。 - Python 3.8
Dict
の問題点
組み込みPythonの組み込みのDict
型は、厳格にkeyとvalueの対応付けを考慮した型付けができません。例えば以下のような関数があったとします。
def show_animal(a):
print(a["name"])
print(a["age"])
この関数は、name
とage
のkeyを持ち、かつname
としてint
を、age
としてint
を持つdict
のみを受け取りたいとします。ただし、keyだけの制限ならLiteral
型を使用すれば制約をかけられます。
from typing import Dict, Literal, Union
Animal = Dict[Literal["name", "age"], Union[str, int]]
def show_animal(a: Animal):
print(a["name"])
print(a["age"])
show_animal({"name": "taro", "age": 5}) # OK
show_animal({"name": "taro", "color": "black"}) # NG
しかし、Literal
型だけではkeyとvalueの対応付けまでは制限をかけることができません。具体的には以下のような例が通ってしまいます。
show_animal({"name": 5, "age": "taro"}) # OK
このようなケースを許容したくない場合があります。
クラスを用いた解決
クラスを使用し、特に直積型のコンテナとして優れたdataclass
を使用すれば、keyとvalueの型の対応付けが保証できます。
from dataclasses import dataclass
@dataclass
class Animal:
name: str
age: int
def show_animal(a: Animal):
print(a.name)
print(a.age)
show_animal(Animal("taro", 5)) # OK
show_animal(Animal(5, "taro")) # NG
しかし、クラスの定義はいいとして関数に渡すときには該当のインスタンスを生成する必要があります。dictのまま扱いたいときにはこの方法はそぐわないかもしれません。
TypedDictを用いた解決
TypedDict
はdictのまま厳格に型を付けることができる便利なクラスです。
from typing import TypedDict
class Animal(TypedDict):
name: str
age: int
def show_animal(a: Animal):
print(a["name"])
print(a["age"])
show_animal({"name": "taro", "age": 5}) # OK
show_animal({"name": "taro", "color": "black"}) # NG
show_animal({"name": 5, "age": "taro"}) # NG
これで望み通りにkeyとvalueの対応に対して型が付けられます。TypedDict
を用いた定義は継承以外にもいくつか方法が用意されているため、好みの方法で利用できます。
# 他の方法
Animal = TypedDict("Animal", name=str, age=int)
Animal = TypedDict("Animal", {"name": str, "age": int})
おわりに
Pythonの型ヒントと型チェックツールを用いた静的型検査は、しっかりと使うことで効果的にコードの品質を向上させることができます。型ヒントの恩恵を受けることで、より堅牢なプログラムを構築してください。