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
dataclassesto 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 isFalse.__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, thenTypeErroris 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 usingslots=Truewill result in the following exception being raised:TypeError: super(type, obj): obj must be an instance or subtype of type. The two-argsuper()is a valid workaround. See gh-90562 for full details.警告
slots=Trueを使用する際に基底クラスの__init_subclass__()にパラメータを渡すと、TypeErrorが発生します。 回避するには、パラメータなしの__init_subclass__を使用するか、デフォルト値を使用してください。 詳細は gh-91126 を参照してください。Added in version 3.10.
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
MISSINGvalue is a sentinel object used to detect if some parameters are provided by the user. This sentinel is used becauseNoneis a valid value for some parameters with a distinct meaning. No code should directly use theMISSINGvalue.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@dataclassdecorator 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: valuepairs. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied withcopy.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 justnameis supplied,typing.Anyis used fortype. 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@dataclassfunction 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=Falsefields work during a call toreplace(). They are not copied from the source object, but rather are initialized in__post_init__(), if they're initialized at all. It is expected thatinit=Falsefields will be rarely and judiciously used. If they are used, it might be wise to have alternate class constructors, or perhaps a customreplace()(or similarly named) method which handles instance copying.データクラスのインスタンスは、汎用関数
copy.replace()でもサポートされています。
- dataclasses.is_dataclass(obj)¶
Return
Trueif its parameter is a dataclass (including subclasses of a dataclass, but not including generic aliases) or an instance of one, otherwise returnFalse.引数がデータクラスのインスタンスである (そして、データクラスそのものではない) かどうかを知る必要がある場合は、
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_ONLYare marked as keyword-only fields. Note that a pseudo-field of typeKW_ONLYis otherwise completely ignored. This includes the name of such a field. By convention, a name of_is used for aKW_ONLYfield. 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
デスクリプタ型のフィールド¶
デフォルト値として デスクリプタオブジェクト が代入されたフィールドは、以下のような特別な動作をします:
データクラスの
__init__()メソッドに渡されたフィールドの値は、デスクリプタオブジェクトを上書きするのではなく、デスクリプタの__set__()メソッドに渡されます。同様に、フィールドを取得または設定すると、デスクリプタオブジェクトを返したり上書きしたりする代わりに、デスクリプタの
__get__()または__set__()メソッドを呼び出します。To determine whether a field contains a default value,
@dataclasswill 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 raisesAttributeErrorin 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
フィールドにデスクリプタ型のアノテーションが付いていても、デフォルト値としてデスクリプタオブジェクトが代入されていなければ、通常のフィールドと同じように動作することに注意してください。