🐍

[Pre-PEP] Inline Syntax for TypedDict

2023/03/09に公開

昨日(2023年3月8日)、Pyright v1.1.297にInline Syntax for TypedDictの実験的サポートが実装されました。Inline Syntax for TypedDictとはどのような機能なのか、何が嬉しいのかを説明しようと思います。

Motivation

従来より、Pythonの辞書型は、typing.TypedDict を利用して型付けを行うことができます。

from typing import TypedDict

class Vector(TypedDict):
  x: float
  y: float
  z: float

vec: Vector = {'x': 0.0, 'y': 1.0, 'z': 0.0}

しかし、TypedDictには欠点があります。それは、class構文を用いて定義しなければいけないことです。使い捨てされる些細なdictに型付けを行うためにすらいちいち定義しないといけません。また、ネストされた辞書型を表現する場合にも、いちいち内側の辞書を別個のTypedDictとして定義する必要がありました。

from typing import TypedDict

class InnerDict(TypedDict):
  a: int
  b: str

class OuterDict(TypedDict):
  a: InnerDict
  b: int
from requests import get
from typing import TypedDict

class Response(TypedDict):
  count: int
  results: list[str]

def hoge() -> list[str]:
  resp = get('https://api.example.com/...')
  ...
  data: Response = resp.json()
  ...
  return data['results']

このようなちょっとしたdictにinlineで型付けができたら嬉しいですよね?

Suggested Syntax

オリジナルの提案では、素のdictリテラルを使用することが提案されていました。

def get_user_data(arg: {'user': {'email': str}}) ->{'email': str}:
   return user['user']

しかし、この書き方にはいくつかの問題があった[1]ため、dictの型引数として渡す構文が提案され、Pyrightに実装されています。

def foo(arg: dict[{'bar': int}]) -> dict[{'baz': str}]:
    ...

Pyrightで正しいinline TypedDictとされるものは以下の通りです。

td1: dict[{"a": int, "b": str}] = {"a": 0, "b": ""}

td2: dict[{"a": dict[{"b": int}]}] = {"a": {"b": 0}}

td3: dict[{"a": "list[float]"}] = {"a": [3]}

# This should generate two errors because dictionary literals can be used
# only with dict or Dict.
err1: list[{"a": 1}]

# This should generate an error because dictionary comprehensions
# are not allowed.
err2: dict[{"a": int for _ in range(1)}]

# This should generate an error because unpacked dictionary
# entries are not allowed.
err3: dict[{**{"a": int}}]

# This should generate three errors because Dict doesn't support inlined
# TypedDict. It generates an exception at runtime.
from typing import Dict
err4: Dict[{"c": int}]

# This should generate an error because an extra type argument is provided.
err5: dict[{"a": int}, str]

現時点では、これをたたき台として新しくPEPを起こす動きになっています。

個人的な感想

なぜ今まで提案されなかったのかと思うほどにシンプルかつメリットの大きい機能です。まだPEPもできていないので、正式な実装までは時間がかかりそうですが、この機能は早くちゃんと使えるようになって欲しい。

脚注
  1. 構文上通常のdictリテラルと区別がつかず、TypedDictのinlineリテラルとして扱うにはPython自体の構文の変更が必要であったこと。あるいはdictを型として扱えるようにするとしても、既存の型システムに大幅な変更を必要とすること(Union型の|とか)。 ↩︎

Discussion