dataclasses --- データクラス

ソースコード: Lib/dataclasses.py


このモジュールは、__init__() や __repr__() のような 特殊メソッド を生成し、ユーザー定義のクラスに自動的に追加するデコレーターや関数を提供します。このモジュールは元々、 PEP 557 で説明されていました。

これらの生成されたメソッドで利用されるメンバー変数は PEP 526 型アノテーションを用いて定義されます。例えば、このコードでは:

from dataclasses import dataclass

@dataclass
class InventoryItem:
    """在庫内の品目を管理するクラス。"""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

とりわけ、以下のような __init__() が追加されます:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand

このメソッドは自動的にクラスに追加される点に留意して下さい。上記の InventoryItem クラスの定義中にこのメソッドが直接明記されるわけではありません。

Added in version 3.7.

モジュールの内容

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)

この関数は、後述する 特殊メソッド を生成し、クラスに追加する decorator です。

@dataclass デコレータは、field を探すためにクラスを検査します。field は 型アノテーション を持つクラス変数として定義されます。後述する2つの例外を除き、 @dataclass は変数アノテーションで指定した型を検査しません。

生成されるすべてのメソッドの中でのフィールドの順序は、それらのフィールドがクラス定義に現れた順序です。

@dataclass デコレータは、後述する様々な "ダンダー" メソッド (訳注:dunderはdouble underscoreの略で、メソッド名の前後にアンダースコアが2つ付いているメソッド) をクラスに追加します。クラスに既にこれらのメソッドが存在する場合の動作は、後述する引数によって異なります。デコレータは呼び出した際に指定したクラスと同じクラスを返します。新しいクラスは生成されません。

@dataclass をパラメータなしの単純なデコレータとして使用すると、このシグネチャに記載されたデフォルト値を指定した場合と同じ動作になります。 つまり、以下の 3 通りの @dataclass の使い方は等価です。

@dataclass
class C:
    ...

@dataclass()
class C:
    ...

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
           match_args=True, kw_only=False, slots=False, weakref_slot=False)
class C:
    ...

@dataclass のパラメータは以下のとおりです:

  • init: 真の場合 (デフォルト)、__init__() メソッドを生成します。

    クラスがすでに __init__() を定義している場合、このパラメータは無視されます。

  • repr: 真の場合 (デフォルト)、__repr__() メソッドを生成します。 生成される repr 文字列にはクラス名と、クラスで定義した順に各フィールドの名前と repr が含まれます。 repr から除外すると指定されたフィールドは含まれません。 例えば、InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10) となります。

    クラスがすでに __repr__() を定義している場合、このパラメータは無視されます。

  • eq: 真の場合 (デフォルト)、__eq__() メソッドを生成します。

    このメソッドは各フィールドを順に比較することでこのクラスのインスタンスを比較します。 比較する両方のインスタンスは、まったく同じ型でなければなりません。

    クラスがすでに __eq__() を定義している場合、このパラメータは無視されます。

    バージョン 3.13 で変更: 生成される __eq__ メソッドは、以前のバージョンのようにフィールドのタプルを比較する代わりに、各フィールドを個別に比較するようになりました (例えば self.a == other.a and self.b == other.b)。

    この変更によって比較が高速になりますが、属性が同一性では等しくても値では等しくない場合 (float('nan') など)、結果が変わる可能性があります。

    Python 3.12 以前では、フィールドのタプルを作成し、それらを比較していました (例えば (self.a, self.b) == (other.a, other.b))。

  • order: 真の場合 (デフォルトは False)、__lt__()、__le__()、__gt__()、__ge__() メソッドを生成します。 これらのメソッドは、フィールドを順に並べたタプルのようにこのクラスのインスタンスを比較します。 比較する両方のインスタンスは、まったく同じ型でなければなりません。 order が真で eq が偽の場合は、ValueError を送出します。

    クラスがすでに __lt__()、__le__()、__gt__()、__ge__() のいずれかを定義している場合は、TypeError を送出します。

  • unsafe_hash: 真の場合、安全でない可能性があっても、dataclasses に __hash__() メソッドを強制的に作成させます。 それ以外の場合、eq と frozen の設定に従って __hash__() メソッドを生成します。 デフォルト値は False です。

    __hash__() は組み込みの hash() で使用されるほか、辞書や集合のようなハッシュを使用するコレクションにオブジェクトを追加する際にも使用されます。 __hash__() があることは、そのクラスのインスタンスが不変であることを示唆します。 可変性は複雑な性質で、プログラマの意図、__eq__() の有無と動作、@dataclass デコレータの eq と frozen フラグの値に依存します。

    デフォルトでは、安全な場合を除き、@dataclass は __hash__() メソッドを暗黙的に追加しません。 また、明示的に定義済みの __hash__() メソッドを追加したり変更したりしません。 クラス属性を __hash__ = None に設定することには、__hash__() のドキュメントで説明されているように、Python における特定の意味があります。

    __hash__() が明示的に定義されていないか、None に設定されている場合、@dataclass は暗黙的な __hash__() メソッドを追加する ことがあります。 推奨されませんが、unsafe_hash=True を指定して、@dataclass に __hash__() メソッドを強制的に作成させることができます。 論理的には不変でも変更可能なクラスの場合に、この方法を使うことが考えられます。 これは特殊な用途であり、慎重に検討する必要があります。

    __hash__() メソッドの暗黙的な作成には、以下の規則が適用されます。 データクラスに明示的な __hash__() メソッドを定義し、同時に unsafe_hash=True を設定することはできません。 この場合は TypeError が発生します。

    eq と frozen がともに真の場合、デフォルトでは @dataclass は __hash__() メソッドを生成します。 eq が真で frozen が偽の場合、__hash__() は None に設定され、ハッシュ不可能であることを示します (可変なので実際にハッシュ不可能です)。 eq が偽の場合、__hash__() は変更されないため、スーパークラスの __hash__() メソッドが使用されます (スーパークラスが object の場合、id に基づくハッシュが使われます)。

  • frozen: 真の場合 (デフォルトは False)、フィールドへの代入で例外が発生します。 これにより、読み取り専用の凍結されたインスタンスをエミュレートします。 後述の 説明 を参照してください。

    クラスで __setattr__() または __delattr__() が定義されており、frozen が真の場合、TypeError を送出します。

  • match_args: 真の場合 (デフォルトは True)、生成される __init__() メソッドのキーワード専用でないパラメータのリストから __match_args__ タプルを作成します (__init__() が生成されない場合も同様です。 上記を参照)。 偽の場合、またはクラスに __match_args__ がすでに定義されている場合は、__match_args__ を生成しません。

Added in version 3.10.

  • kw_only: 真の場合 (デフォルト値は False)、すべてのフィールドをキーワード専用に指定します。 フィールドをキーワード専用にすることの唯一の効果は、そのフィールドから生成される __init__() のパラメータを、__init__() の呼び出し時にキーワードで指定する必要があるという点です。 詳細は用語集の parameter を参照してください。 KW_ONLY の節も参照してください。

    キーワード専用フィールドは __match_args__ に含まれません。

Added in version 3.10.

  • slots: 真の場合 (デフォルトは False)、__slots__ 属性を生成し、元のクラスの代わりに新しいクラスを返します。 クラスに __slots__ がすでに定義されている場合は、TypeError を送出します。

警告

slots=True を使用する際に基底クラスの __init_subclass__() にパラメータを渡すと、TypeError が発生します。 回避するには、パラメータなしの __init_subclass__ を使用するか、デフォルト値を使用してください。 詳細は gh-91126 を参照してください。

Added in version 3.10.

バージョン 3.11 で変更: 基底クラスの __slots__ にフィールド名がすでに含まれている場合、オーバーライド を防ぐため、生成される __slots__ には含まれません。 したがって、データクラスのフィールド名を取得するために __slots__ を使用しないでください。 代わりに fields() を使用してください。 継承するスロットを判定できるように、基底クラスの __slots__ には任意のイテラブルを使用できますが、イテレータは使用 できません。

  • weakref_slot: 真の場合 (デフォルトは False)、インスタンスを 弱参照可能 にするために必要な "__weakref__" という名前のスロットを追加します。 slots=True を指定せずに weakref_slot=True を指定するとエラーになります。

Added in version 3.11.

フィールド には、通常の Python の文法でデフォルト値を指定できます。

@dataclass
class C:
    a: int       # 'a' にはデフォルト値がない
    b: int = 0   # 'b' にデフォルト値を割り当てる

この例では、a と b の両方が、次のように定義される __init__() メソッドに含まれます。

def __init__(self, a: int, b: int = 0):

デフォルト値を指定しないフィールドを、デフォルト値を指定したフィールドの後ろに定義すると、 TypeError が送出されます。これは、単一のクラスであっても、クラス継承の結果でも起きえます。

dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)

一般的で単純な用途では、これ以外の機能は必要ありません。 ただし、データクラスの機能には、フィールドごとの追加情報を必要とするものもあります。 追加情報を指定するために、フィールドのデフォルト値を、提供されている field() 関数の呼び出しに置き換えられます。 例えば次のようにします。

@dataclass
class C:
    mylist: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

上記の MISSING 値は、ユーザーが一部のパラメータを指定したかどうかを検出するための sentinel オブジェクトです。 一部のパラメータでは None が固有の意味を持つ有効な値なので、このセンチネルを使用しています。 MISSING 値を直接使用するコードを書いてはいけません。

field() のパラメータは以下のとおりです:

  • default: 指定した場合、このフィールドのデフォルト値になります。 通常デフォルト値を指定する位置が field() 呼び出しで置き換わるため、このパラメータが必要になります。

  • default_factory: 指定する場合、引数を取らない呼び出し可能オブジェクトでなければなりません。 このフィールドのデフォルト値が必要なときに呼び出されます。 用途の一例として、後述するように、可変なデフォルト値を持つフィールドの指定に使用できます。 default と default_factory を両方指定するとエラーになります。

  • init: 真の場合 (デフォルト)、このフィールドを、生成される __init__() メソッドのパラメータに含めます。

  • repr: 真の場合 (デフォルト)、このフィールドを、生成される __repr__() メソッドが返す文字列に含めます。

  • hash: bool または None を指定できます。 真の場合、このフィールドを、生成される __hash__() メソッドに含めます。 偽の場合、このフィールドを、生成される __hash__() から除外します。 None の場合 (デフォルト)、compare の値を使用します。 比較に使用するフィールドはハッシュにも含めるべきなので、通常はこれが期待される動作です。 この値を None 以外に設定することは推奨されません。

    フィールドのハッシュ値を計算するコストが高い場合に、 hash=False だが compare=True と設定する理由が 1 つあるとすれば、フィールドが等価検査に必要かつ、その型のハッシュ値を計算するのに他のフィールドも使われることです。 フィールドがハッシュから除外されていたとしても、比較には使えます。

  • compare: 真の場合 (デフォルト)、このフィールドを、生成される等価比較および順序比較のメソッド (__eq__()、__gt__() など) に含めます。

  • metadata: マッピングまたは None を指定できます。 None は空の辞書として扱われます。 この値は MappingProxyType() でラップして読み取り専用にし、Field オブジェクト上で公開します。 データクラス自体はこの値を一切使用せず、サードパーティの拡張機構として提供しています。 複数のサードパーティがそれぞれ独自のキーを持ち、メタデータ内の名前空間として使用できます。

  • kw_only: 真の場合、このフィールドをキーワード専用に指定します。 これは、生成される __init__() メソッドのパラメータを算出する際に使用されます。

    キーワード専用フィールドは __match_args__ にも含まれません。

Added in version 3.10.

  • doc: このフィールドの省略可能な docstring です。

Added in version 3.14.

フィールドのデフォルト値を field() の呼び出しで指定した場合、そのフィールドのクラス属性は、指定した default 値で置き換えられます。 default を指定しなかった場合、クラス属性は削除されます。 これは、@dataclass デコレータの実行後、デフォルト値そのものを指定した場合と同じように、クラス属性がすべてフィールドのデフォルト値を持つようにするためです。 例えば、次のコードを実行すると、

@dataclass
class C:
    x: int
    y: int = field(repr=False)
    z: int = field(repr=False, default=10)
    t: int = 20

クラス属性 C.z は 10、クラス属性 C.t は 20 になり、クラス属性 C.x と C.y は設定されません。

バージョン 3.15 で変更: metadata が None の場合、空の dict を包んだ MappingProxyType() の代わりに、空の frozendict を使用するようになりました。

class dataclasses.Field

Field オブジェクトは、定義された各フィールドを記述します。 これらのオブジェクトは内部で作成され、モジュールレベルの fields() 関数 (後述) から返されます。 ユーザーが Field オブジェクトを直接インスタンス化してはいけません。 公開されている属性は以下のとおりです:

  • name: フィールドの名前。

  • type: フィールドの型。

  • default、default_factory、init、repr、hash、compare、metadata、kw_only の意味と値は、field() 関数のものと同じです。

他の属性があることもありますが、それらはプライベートであり、調べたり、依存したりしてはなりません。

class dataclasses.InitVar

InitVar[T] 型アノテーションは、初期化限定 の変数を記述します。 InitVar でアノテーションされたフィールドは疑似フィールドとみなされます。 そのため、fields() 関数から返されず、__init__() および存在する場合は __post_init__() のパラメータとして追加する以外の用途には使われません。

dataclasses.fields(class_or_instance)

このデータクラスのフィールドを定義する Field オブジェクトをタプルで返します。 データクラスあるいはデータクラスのインスタンスを受け付けます。 データクラスやデータクラスのインスタンスが渡されなかった場合は、 TypeError を送出します。 ClassVar や InitVar といった疑似フィールドは返しません。

dataclasses.asdict(obj, *, dict_factory=dict)

データクラス obj を (ファクトリ関数 dict_factory を使用して) 辞書に変換します。 各データクラスは、フィールドを name: value のペアとする辞書に変換されます。 データクラス、辞書、frozendict、リスト、タプルの内部は再帰的に処理されます。 その他のオブジェクトは copy.deepcopy() でコピーされます。

入れ子になったデータクラスに asdict() を使用する例です。

@dataclass
class Point:
     x: int
     y: int

@dataclass
class C:
     mylist: list[Point]

p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}

c = C([Point(0, 0), Point(10, 4)])
assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

浅いコピーを作成するには、以下の方法が使えます。

{field.name: getattr(obj, field.name) for field in fields(obj)}

obj がデータクラスのインスタンスでない場合、asdict() は TypeError を送出します。

dataclasses.astuple(obj, *, tuple_factory=tuple)

データクラス obj を (ファクトリ関数 tuple_factory を使用して) タプルに変換します。 各データクラスは、そのフィールドの値からなるタプルに変換されます。 データクラス、辞書、frozendict、リスト、タプルの内部は再帰的に処理されます。 その他のオブジェクトは copy.deepcopy() でコピーされます。

1つ前の例の続きです:

assert astuple(p) == (10, 20)
assert astuple(c) == ([(0, 0), (10, 4)],)

浅いコピーを作成するには、以下の方法が使えます。

tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))

obj がデータクラスのインスタンスでない場合、astuple() は TypeError を送出します。

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)

名前を cls_name、フィールドを fields の定義、基底クラスを bases に指定したものとする新しいデータクラスを作成し、namespace に指定した名前空間で初期化します。 fields は、各要素が name、(name, type)、(name, type, Field) のいずれかであるイテラブルです。 name だけを指定した場合、type には typing.Any が使用されます。 init、repr、eq、order、unsafe_hash、frozen、match_args、kw_only、slots、weakref_slot の値の意味は @dataclass のものと同じです。

module を指定した場合、データクラスの __module__ 属性はその値に設定されます。 デフォルトでは、呼び出し元のモジュール名に設定されます。

decorator パラメータは、データクラスの作成に使用する呼び出し可能オブジェクトです。 最初の引数としてクラスオブジェクトを取り、@dataclass と同じキーワード引数を受け付ける必要があります。 デフォルトでは @dataclass 関数を使用します。

__annotations__ を持つ新しいクラスを作成する Python の機構であれば、どれでもその後 @dataclass 関数を適用してデータクラスに変換できるため、この関数は必須ではありません。 利便性のために提供されています。 例えば、次のコードは、

C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, field(default=5))],
                   namespace={'add_one': lambda self: self.x + 1})

は、次のコードと等しいです:

@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1

Added in version 3.14: decorator パラメータを追加しました。

dataclasses.replace(obj, /, **changes)

obj と同じ型の新しいオブジェクトを作成し、フィールドを changes の値で置き換えます。 obj がデータクラスでない場合は TypeError を送出します。 changes のキーが指定したデータクラスのフィールド名でない場合は TypeError を送出します。

新たに返すオブジェクトは、データクラスの __init__() メソッドを呼び出して作成します。 これにより、__post_init__() が存在する場合は、それも確実に呼び出されます。

デフォルト値を持たない初期化限定変数がある場合は、__init__() と __post_init__() に渡せるように、replace() の呼び出し時に指定する必要があります。

changes に init=False で定義されたフィールドが含まれているとエラーになります。 この場合は ValueError を送出します。

Be forewarned about how init=False fields work during a call to replace(). They are not copied from the source object, but rather are initialized in __post_init__(), if they're initialized at all. It is expected that init=False fields will be rarely and judiciously used. If they are used, it might be wise to have alternate class constructors, or perhaps a custom replace() (or similarly named) method which handles instance copying.

データクラスのインスタンスは、汎用関数 copy.replace() でもサポートされています。

dataclasses.is_dataclass(obj)

パラメータがデータクラス (データクラスのサブクラスを含みますが、ジェネリックエイリアス は含みません) またはそのインスタンスであれば True を、そうでなければ False を返します。

引数がデータクラスのインスタンスである (そして、データクラスそのものではない) かどうかを知る必要がある場合は、 not isinstance(obj, type) で追加のチェックをしてください:

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)
dataclasses.MISSING

デフォルト値または default_factory が指定されていないことを示す sentinel オブジェクトです。

バージョン 3.15 で変更: MISSING は sentinel オブジェクトになりました。

dataclasses.KW_ONLY

型アノテーションとして使用する sentinel オブジェクトです。 型が KW_ONLY の疑似フィールドより後のフィールドは、すべてキーワード専用フィールドとして指定されます。 型が KW_ONLY の疑似フィールドは、それ以外の点では完全に無視されます。 そのフィールドの名前も無視されます。 慣例では、KW_ONLY フィールドの名前に _ を使用します。 キーワード専用フィールドは、クラスのインスタンス化時にキーワードで指定する必要がある __init__() パラメータを表します。

このサンプルでは y と z がキーワード専用フィールドとなります:

@dataclass
class Point:
    x: float
    _: KW_ONLY
    y: float
    z: float

p = Point(0, y=1.5, z=2.0)

単一のデータクラス内で、型が KW_ONLY のフィールドを複数指定するとエラーになります。

Added in version 3.10.

バージョン 3.15 で変更: KW_ONLY は sentinel オブジェクトになりました。

exception dataclasses.FrozenInstanceError

frozen=True 付きで定義されたデータクラスで、暗黙的に定義された __setattr__() または __delattr__() が呼び出されたときに送出されます。これは AttributeError のサブクラスです。

初期化後の処理

dataclasses.__post_init__()

クラスに定義されている場合、生成された __init__() から、通常は self.__post_init__() として呼び出されます。 ただし、InitVar フィールドが定義されている場合、それらもクラスで定義された順に __post_init__() に渡されます。 __init__() メソッドが生成されない場合、__post_init__() は自動的には呼び出されません。

用途の一例として、他の一つ以上のフィールドに依存するフィールドの値を初期化できます。 例えば次のようにできます。

@dataclass
class C:
    a: float
    b: float
    c: float = field(init=False)

    def __post_init__(self):
        self.c = self.a + self.b

@dataclass が生成する __init__() メソッドは、基底クラスの __init__() メソッドを呼び出しません。 基底クラスに呼び出す必要のある __init__() メソッドがある場合、通常は __post_init__() メソッド内で呼び出します。

class Rectangle:
    def __init__(self, height, width):
        self.height = height
        self.width = width

@dataclass
class Square(Rectangle):
    side: float

    def __post_init__(self):
        super().__init__(self.side, self.side)

ただし、基底クラス自体がデータクラスであれば、派生データクラスがそのすべてのフィールドを初期化するため、一般にデータクラスが生成した __init__() メソッドを呼び出す必要はありません。

下にある初期化限定変数についての節で、 __post_init__() にパラメータを渡す方法を参照してください。 replace() が init=False であるフィールドをどう取り扱うかについての警告も参照してください。

クラス変数

@dataclass が実際にフィールドの型を調べる数少ない場面の 1 つは、フィールドが PEP 526 で定義されるクラス変数かどうかを判定する場合です。 これは、フィールドの型が typing.ClassVar かどうかを調べて行います。 ClassVar であれば、フィールドとしての処理から除外され、データクラスの機構では無視されます。 このような ClassVar 疑似フィールドは、モジュールレベルの fields() 関数から返されません。

初期化限定変数

@dataclass が型アノテーションを調べるもう 1 つの場面は、フィールドが初期化限定変数かどうかを判定する場合です。 これは、フィールドの型が InitVar かどうかを調べて行います。 InitVar であれば、初期化限定フィールドという疑似フィールドとみなされます。 本当のフィールドではないため、モジュールレベルの fields() 関数から返されません。 初期化限定フィールドは、生成される __init__() メソッドのパラメータとして追加され、__post_init__() メソッドがあればそこに渡されます。 データクラスは、それ以外の用途には使用しません。

例えば、インスタンスの作成時に値が渡されなかった場合に、フィールドをデータベースから初期化するものとします。

@dataclass
class C:
    i: int
    j: int | None = None
    database: InitVar[DatabaseType | None] = None

    def __post_init__(self, database):
        if self.j is None and database is not None:
            self.j = database.lookup('j')

c = C(10, database=my_database)

この場合、fields() は i と j の Field オブジェクトを返しますが、database のものは返しません。

凍結されたインスタンス

真に不変な Python オブジェクトを作成することはできません。 ただし、@dataclass デコレータに frozen=True を渡すことで、不変性をエミュレートできます。 この場合、データクラスは __setattr__() と __delattr__() メソッドをクラスに追加します。 これらのメソッドは呼び出されると FrozenInstanceError を送出します。

frozen=True を使用すると、パフォーマンスがわずかに低下します。 __init__() がフィールドの初期化に単純な代入を使えず、object.__setattr__() を使用しなければならないためです。

継承

@dataclass デコレータによるデータクラスの作成時には、クラスのすべての基底クラスを MRO の逆順 (つまり object から始まる順序) に調べ、見つかった各データクラスのフィールドを、順序付きのフィールドのマッピングに追加します。 すべての基底クラスのフィールドを追加した後、自身のフィールドをその順序付きマッピングに追加します。 生成されるすべてのメソッドは、このように計算して統合した順序付きのフィールドのマッピングを使用します。 フィールドは挿入順に並ぶため、派生クラスが基底クラスをオーバーライドします。 例えば次のようになります。

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

最終的なフィールドのリストは、順に x、y、z となります。 x の最終的な型は、クラス C で指定された int です。

C に生成される __init__() メソッドは、次のようになります。

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

__init__() のキーワード専用パラメータの並べ替え

__init__() で必要なパラメータが算出されると、キーワード専用引数は他の一般的な(非キーワード専用)パラメータの後に移動します。これは、すべてのキーワード専用引数は、非キーワード専用パラメータの末尾にこなければならないという、キーワード専用パラメータのPythonの実装の都合で必要なことです。

この例では、Base.y、Base.w、D.t はキーワード専用フィールドで、Base.x と D.z は通常のフィールドです。

@dataclass
class Base:
    x: Any = 15.0
    _: KW_ONLY
    y: int = 0
    w: int = 1

@dataclass
class D(Base):
    z: int = 10
    t: int = field(kw_only=True, default=0)

D に生成される __init__() メソッドは、次のようになります。

def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):

引数の順序はフィールドのリストに現れる順序から変更されています。 通常のフィールドに対応する引数の後に、キーワード専用フィールドに対応する引数が続きます。

並べ替え後の __init__() のパラメータリストでは、キーワード専用パラメータ同士の相対的な順序は保持されます。

デフォルトファクトリ関数

field() に default_factory が指定されている場合、フィールドのデフォルト値が必要になると、引数なしで呼び出されます。 例えば、リストの新しいインスタンスを作成するには、次のようにします。

mylist: list = field(default_factory=list)

フィールドが (init=False によって) __init__() から除外されており、そのフィールドに default_factory も指定されている場合、生成された __init__() 関数からデフォルトファクトリ関数が常に呼び出されます。 これは、フィールドに初期値を与える方法が他にないためです。

可変なデフォルト値

Python はメンバ変数のデフォルト値をクラス属性に保持します。 データクラスを使っていない、この例を考えてみましょう:

class C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x

想定どおり、クラス C の 2 つのインスタンスが同じクラス変数 x を共有していることに注意してください。

データクラスを使っているこのコードが もし仮に 有効なものだとしたら:

@dataclass
class D:
    x: list = []      # このコードは ValueError を送出する
    def add(self, element):
        self.x.append(element)

データクラスは次のようなコードを生成するでしょう:

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x.append(element)

assert D().x is D().x

これには、クラス C を使用した最初の例と同じ問題があります。 つまり、クラスインスタンスを作成する際に x の値を指定しなかったクラス D の 2 つのインスタンスは、同じ x を共有します。 データクラスも通常の Python のクラス作成を使用するだけなので、この動作を共有します。 データクラスがこの状況を検出する一般的な方法はありません。 代わりに、@dataclass デコレータは、ハッシュ不可能なデフォルトのパラメータを検出すると ValueError を送出します。 これは、ハッシュ不可能な値は可変であるという仮定に基づきます。 部分的な解決策ですが、よくある多くの誤りを防げます。

デフォルトファクトリ関数を使うのが、フィールドのデフォルト値として可変な型の新しいインスタンスを作成する手段です:

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

バージョン 3.11 で変更: list、dict、set 型のオブジェクトを検出して禁止する代わりに、ハッシュ不可能なオブジェクトをデフォルト値として禁止するようになりました。 ハッシュ不可能かどうかを、可変かどうかの近似的な判定に使用しています。

デスクリプタ型のフィールド

デフォルト値として デスクリプタオブジェクト が代入されたフィールドは、以下のような特別な動作をします:

  • データクラスの __init__() メソッドに渡されたフィールドの値は、デスクリプタオブジェクトを上書きするのではなく、デスクリプタの __set__() メソッドに渡されます。

  • 同様に、フィールドを取得または設定すると、デスクリプタオブジェクトを返したり上書きしたりする代わりに、デスクリプタの __get__() または __set__() メソッドを呼び出します。

  • フィールドがデフォルト値を持つかどうかを判定するため、@dataclass はデスクリプタの __get__() メソッドを、クラスからアクセスする形式 descriptor.__get__(obj=None, type=cls) で呼び出します。 この場合にデスクリプタが値を返せば、その値をフィールドのデフォルト値として使用します。 一方、この状況でデスクリプタが AttributeError を送出した場合、フィールドにはデフォルト値が設定されません。

class IntConversionDescriptor:
    def __init__(self, *, default):
        self._default = default

    def __set_name__(self, owner, name):
        self._name = "_" + name

    def __get__(self, obj, type):
        if obj is None:
            return self._default

        return getattr(obj, self._name, self._default)

    def __set__(self, obj, value):
        setattr(obj, self._name, int(value))

@dataclass
class InventoryItem:
    quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)

i = InventoryItem()
print(i.quantity_on_hand)   # 100
i.quantity_on_hand = 2.5    # 2.5 を渡して __set__ を呼び出す
print(i.quantity_on_hand)   # 2

フィールドにデスクリプタ型のアノテーションが付いていても、デフォルト値としてデスクリプタオブジェクトが代入されていなければ、通常のフィールドと同じように動作することに注意してください。