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.
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=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)¶
パラメータがデータクラス (データクラスのサブクラスを含みますが、ジェネリックエイリアス は含みません) またはそのインスタンスであれば
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
デスクリプタ型のフィールド¶
デフォルト値として デスクリプタオブジェクト が代入されたフィールドは、以下のような特別な動作をします:
データクラスの
__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
フィールドにデスクリプタ型のアノテーションが付いていても、デフォルト値としてデスクリプタオブジェクトが代入されていなければ、通常のフィールドと同じように動作することに注意してください。