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: If true (the default), an __eq__() method will be generated.

    This method compares the class by comparing each field in order. Both instances in the comparison must be of the identical type.

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

    バージョン 3.13 で変更: The generated __eq__ method now compares each field individually (for example, self.a == other.a and self.b == other.b), rather than comparing tuples of fields as in previous versions.

    This change makes the comparison faster but it may alter results in cases where attributes compare equal by identity but not by value (such as float('nan')).

    In Python 3.12 and earlier, the comparison was performed by creating tuples of the fields and comparing them (for example, (self.a, self.b) == (other.a, other.b)).

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

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

  • unsafe_hash: If true, force dataclasses to create a __hash__() method, even though it may not be safe to do so. Otherwise, generate a __hash__() method according to how eq and frozen are set. The default value is 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: If true (the default is False), assigning to fields will generate an exception. This emulates read-only frozen instances. See the discussion below.

    If __setattr__() or __delattr__() is defined in the class and frozen is true, then TypeError is raised.

  • 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 を送出します。

警告

Calling no-arg super() in dataclasses using slots=True will result in the following exception being raised: TypeError: super(type, obj): obj must be an instance or subtype of type. The two-arg super() is a valid workaround. See gh-90562 for full details.

警告

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)

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

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

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

As shown above, the MISSING value is a sentinel object used to detect if some parameters are provided by the user. This sentinel is used because None is a valid value for some parameters with a distinct meaning. No code should directly use the MISSING value.

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.

If the default value of a field is specified by a call to field(), then the class attribute for this field will be replaced by the specified default value. If default is not provided, then the class attribute will be deleted. The intent is that after the @dataclass decorator runs, the class attributes will all contain the default values for the fields, just as if the default value itself were specified. For example, after:

@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 は設定されません。

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)

Converts the dataclass obj to a dict (by using the factory function dict_factory). Each dataclass is converted to a dict of its fields, as name: value pairs. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied with 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)

Converts the dataclass obj to a tuple (by using the factory function tuple_factory). Each dataclass is converted to a tuple of its field values. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied with 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)

Creates a new dataclass with name cls_name, fields as defined in fields, base classes as given in bases, and initialized with a namespace as given in namespace. fields is an iterable whose elements are each either name, (name, type), or (name, type, Field). If just name is supplied, typing.Any is used for type. The values of init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, and weakref_slot have the same meaning as they do in @dataclass.

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

This function is not strictly required, because any Python mechanism for creating a new class with __annotations__ can then apply the @dataclass function to convert that class to a dataclass. This function is provided as a convenience. For example:

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
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)

Return True if its parameter is a dataclass (including subclasses of a dataclass, but not including generic aliases) or an instance of one, otherwise return False.

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

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

デフォルト値やdefault_factoryが設定されてない場合の番兵の値を設定します。

dataclasses.KW_ONLY

A sentinel value used as a type annotation. Any fields after a pseudo-field with the type of KW_ONLY are marked as keyword-only fields. Note that a pseudo-field of type KW_ONLY is otherwise completely ignored. This includes the name of such a field. By convention, a name of _ is used for a KW_ONLY field. Keyword-only fields signify __init__() parameters that must be specified as keywords when the class is instantiated.

このサンプルでは 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.

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

The __init__() method generated by @dataclass does not call base class __init__() methods. If the base class has an __init__() method that has to be called, it is common to call this method in a __post_init__() method:

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 であるフィールドをどう取り扱うかについての警告も参照してください。

クラス変数

One of the few places where @dataclass actually inspects the type of a field is to determine if a field is a class variable as defined in PEP 526. It does this by checking if the type of the field is typing.ClassVar. If a field is a ClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. Such ClassVar pseudo-fields are not returned by the module-level fields() function.

初期化限定変数

Another place where @dataclass inspects a type annotation is to determine if a field is an init-only variable. It does this by seeing if the type of a field is of type InitVar. If a field is an InitVar, it is considered a pseudo-field called an init-only field. As it is not a true field, it is not returned by the module-level fields() function. Init-only fields are added as parameters to the generated __init__() method, and are passed to the optional __post_init__() method. They are not otherwise used by dataclasses.

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

@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 のものは返しません。

凍結されたインスタンス

It is not possible to create truly immutable Python objects. However, by passing frozen=True to the @dataclass decorator you can emulate immutability. In that case, dataclasses will add __setattr__() and __delattr__() methods to the class. These methods will raise a FrozenInstanceError when invoked.

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

継承

When the dataclass is being created by the @dataclass decorator, it looks through all of the class's base classes in reverse MRO (that is, starting at object) and, for each dataclass that it finds, adds the fields from that base class to an ordered mapping of fields. After all of the base class fields are added, it adds its own fields to the ordered mapping. All of the generated methods will use this combined, calculated ordered mapping of fields. Because the fields are in insertion order, derived classes override base classes. An example:

@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

This has the same issue as the original example using class C. That is, two instances of class D that do not specify a value for x when creating a class instance will share the same copy of x. Because dataclasses just use normal Python class creation they also share this behavior. There is no general way for Data Classes to detect this condition. Instead, the @dataclass decorator will raise a ValueError if it detects an unhashable default parameter. The assumption is that if a value is unhashable, it is mutable. This is a partial solution, but it does protect against many common errors.

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

@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__() メソッドを呼び出します。

  • To determine whether a field contains a default value, @dataclass will call the descriptor's __get__() method using its class access form: descriptor.__get__(obj=None, type=cls). If the descriptor returns a value in this case, it will be used as the field's default. On the other hand, if the descriptor raises AttributeError in this situation, no default value will be provided for the field.

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

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