🤔

PEP681 - Data Class Transformsって何のためにあるの?

2022/12/08に公開

この記事はMapbox Japan Advent Calendar 2022 8日目の記事です。

PEP681が追加された背景

PEP681 - Data Class Transformstypingモジュールに@dataclass_transformというデコレータを追加する変更でpydanticSQLAlchemypyserde等のdataclassをベースにしたライブラリの型チェックし易くするために提案されました。

例えば、@dataclassを追加するだけの超シンプルなデコレータ@add_dataclassがあるとします。

from dataclasses import dataclass
from typing import Type, TypeVar

def add_dataclass(cls: Type[T]) -> Type[T]:
    """ @dataclassを追加するだけのデコレータ """
    dataclass(cls)
    return cls

@add_dataclass
class A:
    v: int

a = A(10)
print(a)

このコードを実行すると意図した通りに動いてくれるんですが、

$ python a.py
A(v=10)

mypyで型チェックをすると、以下のようにエラーが出てしまいます。

$ mypy a.py
a.py:15: error: Too many arguments for "A"

なんでmypyが正しくチェックしてくれないかというと、タイプチェッカーはdataclassを解釈して型アノテーションに基づいてコンストラクタ等のメソッドの型チェックをしてくれるんですが、実際にPythonコードを実行している訳ではないのでadd_dataclassの内部でdataclassが呼ばれようが型チェックをしてくれません。タイプチェッカー的にはdataclassデコレータは付いてないことになるので上記の型エラーが出力されるわけです。

PEP681ができる前まではどうしてた?

じゃあ、PEP681ができる前まではどうしていたかというと、

1. 必ず@dataclassデコレータを付けるようにする
@add_dataclass
@dataclass
class A:
    v: int

この例だとdataclassを付けるだけのデコレータにさらに@dataclass付けているので意味がなくなってしまいますが、タイプチェッカーのエラーはなくなります。

pyserdeはこのアプローチをとっていて、本来@dataclassがなくても機能しますが、付けることが推奨されています。

@serde
@dataclass  # <= 必須ではないが推奨
class Foo:
    ...
2. typing.TYPE_CHECKINGでタイプチェッカーを騙す

typing.TYPE_CHECKINGという型結チェック時だけTrueになる変数があるので、以下のようにadd_dataclassdataclassであるかのようにエイリアスを作るとタイプチェッカーを騙すことができます。

if TYPE_CHECKING:
    from dataclasses import dataclass as add_dataclass
else:
    def add_dataclass(cls: Type[T]) -> Type[T]:
        dataclass(cls)
        return cls

これでエラーが出なくなりました。

$ mypy a.py
Success: no issues found in 1 source file

確かpydanticもこのアプローチだったと思います。

PEP681

Python 3.11で追加されたtyping.datalass_transformを使えば、もっと簡単に解決できるようになります。 @typing.dataclass_transform()をデコレータ関数に付けるだけ。

from typing import Type, TypeVar, dataclass_transform

@dataclass_transform()
def add_dataclass(cls: Type[T]) -> Type[T]:
    """ @dataclassを追加するだけのデコレータ """
    dataclass(cls)
    return cls

Python 3.7~3.10の場合は、type_extensions>=4.1.0にバックポートがあるのでそちらを使いましょう。

タイプチェッカーのPEP681サポート状況

が、残念ながら実はmypyは2022年11月11日現在でPEP681をサポートしていません 🥹

主なタイプチェッカーのPEP681サポート状況は以下の通り

mypy 0.990 pyright 1.1279 pytype 2022.11.10 pyre 0.9.17
未対応 対応 未対応 未対応

唯一対応しているpyrightで型チェックをするとエラーは何も出なくなりました 🎉

$ pyright a.py
0 errors, 0 warnings, 0 informations

宣伝

dataclassserdeデコレータを付けるだけで、Toml,Yaml,JSON,MsgPack等の様々なフォーマットに(de)serializeできるライブラリpyserdeを開発しています。よかったら使ってみてください 🙏
https://github.com/yukinarit/pyserde

また、pyserdeでPEP681に対応した時のPRはこちらになります。参考にしてください。

GitHubで編集を提案

Discussion