データクラス

型チェッカーは、dataclasses モジュールを通じて作成されたデータクラスをサポートする必要があります。 さらに、型システムには、サードパーティのクラスを標準のデータクラスのように動作させるメカニズムが含まれています。

dataclass_transform デコレーター

(元々 PEP 681 で指定されています。)

仕様

この仕様では、typing モジュールに dataclass_transform() という名前のデコレーター関数について説明します。 このデコレーターは、デコレーター自体である関数、クラス、またはメタクラスに適用できます。 dataclass_transform の存在は、デコレーター関数、クラス、またはメタクラスがクラスを変換し、データクラスのような動作を付与するランタイムの「マジック」を実行することを静的型チェッカーに伝えます。

dataclass_transform が関数に適用される場合、デコレーターとして装飾された関数を使用することは、データクラスのようなセマンティクスを適用するものと見なされます。 関数にオーバーロードがある場合、dataclass_transform デコレーターは関数の実装またはオーバーロードのいずれか 1 つに適用できますが、複数のオーバーロードには適用できません。 オーバーロードに適用される場合でも、dataclass_transform デコレーターは関数のすべての使用に影響を与えます。

dataclass_transform がクラスに適用される場合、データクラスのようなセマンティクスは、装飾されたクラスから直接または間接的に派生するクラスや、装飾されたクラスをメタクラスとして使用するクラスに対して適用されるものと見なされます。 装飾されたクラスおよびその基本クラスの属性はフィールドとは見なされません。

各アプローチの例は、以下のセクションに示されています。 各例では、CustomerModel クラスをデータクラスのようなセマンティクスで作成します。 装飾されたオブジェクトの実装は簡潔さのために省略されていますが、クラスを次のように変更するものと仮定します。

  • クラス内およびその親クラス内で宣言されたデータフィールドを使用して __init__ メソッドを合成します。

  • __eq__ および __ne__ メソッドを合成します。

型チェッカーは、CustomerModel クラスが合成された __init__ メソッドを使用してインスタンス化できることを認識します。

# 位置引数を使用
c1 = CustomerModel(327, "John Smith")

# キーワード引数を使用
c2 = CustomerModel(id=327, name="John Smith")

# これらの呼び出しはランタイムエラーを生成し、静的型チェッカーによってエラーとしてフラグ付けされるべきです。
c3 = CustomerModel()
c4 = CustomerModel(327, first_name="John")
c5 = CustomerModel(327, "John Smith", 0)

デコレーター関数の例

_T = TypeVar("_T")

# ``create_model`` デコレーターはライブラリによって定義されます。
# これはタイプスタブまたはインラインで行うことができます。
@typing.dataclass_transform()
def create_model(cls: Type[_T]) -> Type[_T]:
    cls.__init__ = ...
    cls.__eq__ = ...
    cls.__ne__ = ...
    return cls

# ``create_model`` デコレーターは次のように新しいモデルクラスを作成するために使用できます。
@create_model
class CustomerModel:
    id: int
    name: str

クラスの例

# ``ModelBase`` クラスはライブラリによって定義されます。 これはタイプスタブまたはインラインで行うことができます。
@typing.dataclass_transform()
class ModelBase: ...

# ``ModelBase`` クラスは次のように新しいモデルサブクラスを作成するために使用できます。
class CustomerModel(ModelBase):
    id: int
    name: str

メタクラスの例

# ``ModelMeta`` メタクラスおよび ``ModelBase`` クラスはライブラリによって定義されます。 これはタイプスタブまたはインラインで行うことができます。
@typing.dataclass_transform()
class ModelMeta(type): ...

class ModelBase(metaclass=ModelMeta): ...

# ``ModelBase`` クラスは次のように新しいモデルサブクラスを作成するために使用できます。
class CustomerModel(ModelBase):
    id: int
    name: str

デコレーター関数およびクラス/メタクラスのパラメータ

データクラスのような機能を提供するデコレーター関数、クラス、またはメタクラスは、特定の動作を変更するパラメータを受け入れる場合があります。 この仕様では、データクラス変換によって使用される場合に静的型チェッカーが尊重しなければならない次のパラメータを定義します。 これらのパラメータは bool 引数を受け入れ、bool 値(True または False)を静的に評価できる必要があります。

  • eqorderfrozeninit および unsafe_hash は、標準ライブラリのデータクラスでサポートされているパラメータであり、その意味は PEP 557 で定義されています。

  • kw_onlymatch_args および slots は、標準ライブラリのデータクラスでサポートされているパラメータであり、Python 3.10 で初めて導入されました。

dataclass_transform パラメータ

dataclass_transform のパラメータは、デフォルトの動作を基本的にカスタマイズするためのものです。

_T = TypeVar("_T")

def dataclass_transform(
    *,
    eq_default: bool = True,
    order_default: bool = False,
    kw_only_default: bool = False,
    frozen_default: bool = False,
    field_specifiers: tuple[type | Callable[..., Any], ...] = (),
    **kwargs: Any,
) -> Callable[[_T], _T]: ...
  • eq_default は、呼び出し元が eq パラメータを省略した場合に True または False と見なすかどうかを示します。 指定されていない場合、eq_default は True にデフォルト設定されます(データクラスのデフォルトの仮定)。

  • order_default は、呼び出し元が order パラメータを省略した場合に True または False と見なすかどうかを示します。 指定されていない場合、order_default は False にデフォルト設定されます(データクラスのデフォルトの仮定)。

  • kw_only_default は、呼び出し元が kw_only パラメータを省略した場合に True または False と見なすかどうかを示します。 指定されていない場合、kw_only_default は False にデフォルト設定されます(データクラスのデフォルトの仮定)。

  • frozen_default は、呼び出し元が frozen パラメータを省略した場合に True または False と見なすかどうかを示します。 指定されていない場合、frozen_default は False にデフォルト設定されます(データクラスのデフォルトの仮定)。

  • field_specifiers は、フィールドを記述するサポートされるクラスの静的リストを指定します。 一部のライブラリは、フィールド指定子のインスタンスを割り当てる関数も提供しており、これらの関数もこのタプルに指定できます。 指定されていない場合、field_specifiers は空のタプル(フィールド指定子はサポートされていない)にデフォルト設定されます。 標準のデータクラスの動作は、Field と呼ばれる 1 種類のフィールド指定子と、このクラスのインスタンスを作成するヘルパー関数(field)のみをサポートするため、標準ライブラリのデータクラスの動作を説明する場合、タプル引数 (dataclasses.Field, dataclasses.field) を提供します。

  • kwargs は、dataclass_transform に任意の追加のキーワード引数を渡すことを可能にします。 これにより、型チェッカーは typing.py の変更を待たずに実験的なパラメータをサポートする自由を得ることができます。 型チェッカーは、認識されないパラメータに対してエラーを報告する必要があります。

将来的には、ユーザーコードで一般的な動作をサポートするために必要に応じて、dataclass_transform に追加のパラメータを追加する場合があります。 これらの追加は、追加の PEP を介さずに typing-sig での合意に達した後に行われます。

次のセクションでは、これらのパラメータの使用方法を示す追加の例を提供します。

デコレーター関数の例

# ``create_model`` 関数がキーワード専用パラメータを合成された ``__init__`` メソッドに仮定することを示します。 ``kw_only=False`` で呼び出されない限り。 常に順序関連のメソッドを合成し、この動作を上書きする方法を提供しません。
@typing.dataclass_transform(kw_only_default=True, order_default=True)
def create_model(
    *,
    frozen: bool = False,
    kw_only: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...

# このデコレーターがこのライブラリからインポートされたコードによってどのように使用されるかの例:
@create_model(frozen=True, kw_only=False)
class CustomerModel:
    id: int
    name: str

クラスの例

# このクラスから派生するクラスが比較メソッドを合成することをデフォルトとすることを示します。
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelBase:
    def __init_subclass__(
        cls,
        *,
        init: bool = True,
        frozen: bool = False,
        eq: bool = True,
        order: bool = True,
    ):
        ...

# このクラスがこのライブラリからインポートされたコードによってどのように使用されるかの例:
class CustomerModel(
    ModelBase,
    init=False,
    frozen=True,
    eq=False,
    order=False,
):
    id: int
    name: str

メタクラスの例

# このメタクラスを使用するクラスが比較メソッドを合成することをデフォルトとすることを示します。
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelMeta(type):
    def __new__(
        cls,
        name,
        bases,
        namespace,
        *,
        init: bool = True,
        frozen: bool = False,
        eq: bool = True,
        order: bool = True,
    ):
        ...

class ModelBase(metaclass=ModelMeta):
    ...

# このクラスがこのライブラリからインポートされたコードによってどのように使用されるかの例:
class CustomerModel(
    ModelBase,
    init=False,
    frozen=True,
    eq=False,
    order=False,
):
    id: int
    name: str

フィールド指定子

データクラスのようなセマンティクスをサポートするほとんどのライブラリは、クラス定義がクラス内の各フィールドに関する追加のメタデータを提供できるようにする 1 つ以上の「フィールド指定子」タイプを提供します。 このメタデータは、たとえばデフォルト値を記述したり、フィールドが合成された __init__ メソッドに含まれるかどうかを示したりすることができます。

追加のメタデータが必要ない場合、フィールド指定子を省略できます。

@dataclass
class Employee:
    # フィールド指定子なしのフィールド
    name: str

    # フィールド指定子クラスインスタンスを使用するフィールド
    age: Optional[int] = field(default=None, init=False)

    # デフォルト値を記述するための型注釈と単純な初期化子を持つフィールド
    is_paid_hourly: bool = True

    # 型注釈が提供されていないため、フィールドではなく(クラス変数)
    office_number = "unassigned"

フィールド指定子のパラメータ

データクラスのようなセマンティクスをサポートし、フィールド指定子クラスをサポートするライブラリは、通常、これらのフィールド指定子を構築するために共通のパラメータ名を使用します。 この仕様では、静的型チェッカーが理解しなければならないパラメータの名前と意味を正式化します。 これらの標準化されたパラメータはキーワード専用でなければなりません。

これらのパラメータは、comparehash など、型チェックに影響を与えないものを除く、dataclasses.field() でサポートされているもののスーパーセットです。

フィールド指定子クラスは、コンストラクタ内で他のパラメータを使用することができ、これらのパラメータは位置指定であり、他の名前を使用することができます。

  • init は、フィールドが合成された __init__ メソッドに含まれるかどうかを示すオプションの bool パラメータです。 指定されていない場合、init は True にデフォルト設定されます。 フィールド指定子関数は、リテラル bool 値型(Literal[False] または Literal[True])を使用して init の値を暗黙的に指定するオーバーロードを使用できます。

  • default は、フィールドのデフォルト値を提供するオプションのパラメータです。

  • default_factory は、フィールドのデフォルト値を返すランタイムコールバックを提供するオプションのパラメータです。 default または default_factory のいずれも指定されていない場合、フィールドにはデフォルト値がないと見なされ、クラスのインスタンス化時に値を提供する必要があります。

  • factorydefault_factory のエイリアスです。 標準ライブラリのデータクラスは default_factory という名前を使用しますが、attrs は多くのシナリオで factory という名前を使用するため、このエイリアスは attrs をサポートするために必要です。

  • kw_only は、フィールドがキーワード専用としてマークされるかどうかを示すオプションの bool パラメータです。 True の場合、フィールドはキーワード専用になります。 False の場合、キーワード専用にはなりません。 指定されていない場合、dataclass_transform で装飾されたオブジェクトの kw_only パラメータの値が使用されます。 それも指定されていない場合、dataclass_transformkw_only_default の値が使用されます。

  • alias は、フィールドの代替名を提供するオプションの str パラメータです。 この代替名は、合成された __init__ メソッドで使用されます。

  • converter は、フィールドに値を割り当てる際に使用される呼び出し可能なオプションのパラメータです。

defaultdefault_factory および factory のいずれかを複数指定することはエラーです。

次の例は上記を示しています。

# ライブラリコード(タイプスタブまたはインライン内)
# このライブラリでは、リゾルバを渡すと init は False でなければならず、Literal[False] を持つオーバーロードがそれを強制します。
@overload
def model_field(
        *,
        default: Optional[Any] = ...,
        resolver: Callable[[], Any],
        init: Literal[False] = False,
    ) -> Any: ...

@overload
def model_field(
        *,
        default: Optional[Any] = ...,
        resolver: None = None,
        init: bool = True,
    ) -> Any: ...

@typing.dataclass_transform(
    kw_only_default=True,
    field_specifiers=(model_field, ))
def create_model(
    *,
    init: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...

# このライブラリからインポートされたコード:
@create_model(init=False)
class CustomerModel:
    id: int = model_field(resolver=lambda : 0)
    name: str

ランタイムの動作

ランタイムでは、dataclass_transform デコレーターの唯一の効果は、装飾された関数またはクラスに __dataclass_transform__ という名前の属性を設定してインスペクションをサポートすることです。 属性の値は、dataclass_transform パラメータの名前をその値にマッピングする辞書である必要があります。

たとえば:

{
  "eq_default": True,
  "order_default": False,
  "kw_only_default": False,
  "field_specifiers": (),
  "kwargs": {}
}

データクラスのセマンティクス

明示的に述べられていない限り、dataclass_transform で影響を受けるクラスは、dataclass_transform で装飾されたクラスを継承するか、dataclass_transform で装飾された関数で装飾されることによって、標準ライブラリの dataclass() のように動作するものと見なされます。

これには、次のセマンティクスが含まれますが、これに限定されません。

  • 凍結されたデータクラスは、非凍結データクラスを継承できません。 dataclass_transform で装飾されたクラスは、凍結されているとも非凍結されているとも見なされないため、凍結されたクラスがそれを継承することができます。 同様に、dataclass_transform で装飾されたメタクラスを直接指定するクラスは、凍結されているとも非凍結されているとも見なされません。

    次のクラスの例を考えてみましょう。

    # ModelBase は ``dataclass_transform`` で装飾されているため、「凍結」または「非凍結」と見なされません。
    @typing.dataclass_transform()
    class ModelBase(): ...
    
    # Vehicle は「frozen=True」を指定していないため、非凍結と見なされます。
    class Vehicle(ModelBase):
        name: str
    
    # Car は凍結されたクラスであり、非凍結クラスである Vehicle から派生しています。 これはエラーです。
    class Car(Vehicle, frozen=True):
        wheel_count: int
    

    そして、これらの類似したメタクラスの例:

    @typing.dataclass_transform()
    class ModelMeta(type): ...
    
    # ModelBase は、ModelMeta をメタクラスとして直接指定しているため、「凍結」または「非凍結」と見なされません。
    class ModelBase(metaclass=ModelMeta): ...
    
    # Vehicle は「frozen=True」を指定していないため、非凍結と見なされます。
    class Vehicle(ModelBase):
        name: str
    
    # Car は凍結されたクラスであり、非凍結クラスである Vehicle から派生しています。 これはエラーです。
    class Car(Vehicle, frozen=True):
        wheel_count: int
    
  • フィールドの順序と継承は、Python ドキュメント で指定されたルールに従うものと見なされます。 これには、オーバーライドの影響(親クラスですでに定義されているフィールドを子クラスで再定義すること)が含まれます。

  • PEP 557 は、デフォルト値のないすべてのフィールドがデフォルト値のあるフィールドの前に表示される必要があることを示しています。 PEP 557 では明示的に述べられていませんが、このルールは init=False の場合には無視され、この仕様でもその状況ではこの要件を無視します。 同様に、__init__ のキーワード専用パラメータが使用される場合、この順序を強制する必要はないため、kw_only セマンティクスが有効な場合、このルールは強制されません。

  • dataclass と同様に、クラス内で明示的に宣言されたメソッドがある場合、メソッドの合成はスキップされます。 基本クラスのメソッド宣言は、メソッドの合成をスキップする原因にはなりません。

    たとえば、クラスが __init__ メソッドを明示的に宣言している場合、そのクラスには __init__ メソッドは合成されません。

  • KW_ONLY センチネル値は、Python ドキュメント および bpo-43532 で説明されているようにサポートされています。

  • ClassVar 属性はデータクラスフィールドとは見なされず、データクラスメカニズムによって無視されます

  • データクラスフィールドは Final[...] で注釈を付けることができます。 たとえば、データクラス本体内の x: Final[int] は、合成された __init__ メソッドで初期化され、その後は代入できないデータクラスフィールド x を指定します。 クラス本体内で初期化された Final データクラスフィールドは、明示的に ClassVar で注釈されない限り、クラス属性ではありません。 たとえば、x: Final[int] = 3 は、合成された __init__ メソッドでデフォルト値 3 を持つデータクラスフィールド x です。 データクラスの最終クラス変数は、たとえば x: ClassVar[Final[int]] = 3 のように明示的に注釈を付ける必要があります。

コンバーター

converter パラメータは、関連する属性に値を割り当てる際に使用される呼び出し可能なものを提供するためにフィールド定義で指定できます。 この機能により、属性の割り当て中に自動的な型変換と検証が可能になります。

コンバーターの動作:

  • コンバーターは、デフォルト値の割り当て、合成された __init__ メソッドでの割り当て、および直接の属性設定(例:obj.attr = value)を含むすべての属性割り当てに使用されます。

  • コンバーターは属性の読み取り時には使用されません。属性はすでに変換されているはずです。

コンバーターの型付けルール:

  • converter は、単一の位置引数を受け入れる呼び出し可能なものでなければなりません(ただし、他のオプションの引数を受け入れることができますが、型付け目的では無視されます)。

  • 最初の位置引数の型は、フィールドの合成された __init__ パラメータの型を提供します。

  • 呼び出し可能なものの戻り値の型は、フィールドの宣言された型に代入可能でなければなりません。

  • default または default_factory が提供されている場合、デフォルト値の型は converter の最初の位置引数に代入可能でなければなりません。

使用例:

def str_or_none(x: Any) -> str | None:
    return str(x) if x is not None else None

@custom_dataclass
class Example:
    int_field: int = custom_field(converter=int)
    str_field: str | None = custom_field(converter=str_or_none)
    path_field: pathlib.Path = custom_field(
        converter=pathlib.Path,
        default="default/path.txt"
    )

# 使用例
example = Example("123", None, "some/path")
# example.int_field == 123
# example.str_field == None
# example.path_field == pathlib.Path("some/path")

未定義の動作

複数の dataclass_transform デコレーターが見つかった場合、単一の関数(そのオーバーロードを含む)、単一のクラス、またはクラス階層内で、結果の動作は未定義です。 ライブラリの作成者はこれらのシナリオを避けるべきです。