dataclasses --- データクラス¶
ソースコード: Lib/dataclasses.py
このモジュールは、__init__() や __repr__() のような special method を生成し、ユーザー定義のクラスに自動的に追加するデコレータや関数を提供します。このモジュールは PEP 557 に記載されました。
これらの生成されたメソッドで利用されるメンバー変数は PEP 526 型アノテーションを用いて定義されます。例えば、このコードでは:
from dataclasses import dataclass
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
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 クラスの定義中にこのメソッドが直接明記されるわけではありません。
バージョン 3.7 で追加.
モジュールレベルのデコレータ、クラス、関数¶
-
@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)¶ この関数は、後述する special method を生成し、クラスに追加する decorator です。
dataclass()デコレータは、フィールドを探すためにクラスを検査します。フィールドは 型アノテーション を持つクラス変数として定義されます。 後述する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) class C: ...
dataclass()の引数は以下の通りです:init: (デフォルトの)真の場合、__init__()メソッドが生成されます。もしクラスに
__init__()が既に定義されていた場合は、この引数は無視されます。repr: (デフォルトの)真の場合、__repr__()メソッドが生成されます。 生成された repr 文字列には、クラス名、各フィールドの名前および repr 文字列が、クラス上での定義された順序で並びます。 repr から除外するように印が付けられたフィールドは、 repr 文字列には含まれません。 例えば、このようになります:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)。もしクラスに
__repr__()が既に定義されていた場合は、この引数は無視されます。eq: (デフォルトの)真の場合、__eq__()メソッドが生成されます。このメソッドはクラスの比較を、そのクラスのフィールドからなるタプルを比較するように行います。比較する2つのインスタンスのクラスは同一でなければなりません。もしクラスに
__eq__()が既に定義されていた場合は、この引数は無視されます。order: 真 (デフォルト値はFalse) の場合、__lt__()、__le__()、__gt__()、__ge__()メソッドが生成されます。これらの比較は、クラスをそのフィールドからなるタプルであるかのように取り扱います。比較される2つのインスタンスは、同一の型でなければなりません。もしorderが true で、eqに falseを指定すすると、ValueErrorが送出されます。もし、クラスで既に
__lt__(),__le__(),__gt__(),__ge__()のうちいずれかが定義されているとTypeErrorが送出されます。unsafe_hash: (デフォルトの)Falseの場合、eqとfrozenがどう設定されているかに従って__hash__()メソッドが生成されます。__hash__()は、組み込みのhash()から使われたり、 dict や set のようなハッシュ化されたコレクションにオブジェクトを追加するときに使われます。__hash__()があるということはそのクラスのインスタンスが不変 (イミュータブル) であることを意味します。 可変性というのは複雑な性質で、プログラマの意図、__eq__()が存在しているかどうかとその振る舞い、dataclass()デコレータのeqフラグとfrozenフラグの値に依存します。デフォルトでは、
dataclass()は追加しても安全でない限り__hash__()メソッドを暗黙的には追加しません。 また、明示的に定義され存在している__hash__()メソッドに追加したり変更したりはしません。 クラスの属性の__hash__ = Noneという設定は、 Python にとって__hash__()のドキュメントにあるような特別な意味があります。__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__()がクラスに定義されていた場合は、TypeErrorが送出されます。 後にある議論を参照してください。
フィールドには、通常の Python の文法でデフォルト値を指定できます。@dataclass class C: a: int # 'a' has no default value b: int = 0 # assign a default value for 'b'
この例では、生成された
__init__()メソッドにはaとbの両方が含まれ、以下のように定義されます:def __init__(self, a: int, b: int = 0):
デフォルト値を指定しないフィールドを、デフォルト値を指定したフィールドの後ろに定義すると、
TypeErrorが送出されます。これは、単一のクラスであっても、クラス継承の結果でも起きえます。
-
dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)¶ 通常の単純なユースケースでは、この他の機能は必要ありません。 しかし、データクラスには、フィールドごとの情報を必要とする機能もあります。 追加の情報の必要性に応えるために、デフォルトのフィールドの値をモジュールから提供されている
field()関数の呼び出しに置き換えられます。 例えば次のようになります:@dataclass class C: mylist: list[int] = field(default_factory=list) c = C() c.mylist += [1, 2, 3]
上にあるように、
MISSING値はdefaultパラメータとdefault_factoryパラメータが提供されたかどうかを検出するのに使われる番兵オブジェクトです。 この番兵が使われるのは、Noneがdefaultの有効な値だからです。 どんなコードでもMISSING値を直接使うべきではありません。field()の引数は次の通りです:default: 与えられた場合、このフィールドのデフォルト値になります。 これが必要なのは、field()の呼び出しそのものが通常ではデフォルト値がいる位置を横取りしているからです。default_factory: 提供されていた場合、0 引数の呼び出し可能オブジェクトでなければならず、このフィールドの初期値が必要になったときに呼び出されます。 他の目的も含めて、下で議論されているように、フィールドに可変なデフォルト値を指定するのに使えます。defaultとdefault_factoryの両方を指定するとエラーになります。init: (デフォルトの)真の場合、 生成される__init__()メソッドの引数にこのフィールドを含めます。repr: (デフォルトの)真の場合、生成される__repr__()メソッドによって返される文字列に、このフィールドを含めます。compare: (デフォルトの) 真の場合、生成される等価関数と比較関数(__eq__()、__gt__()など)にこのフィールドを含めます。hash: これは真偽値あるいはNoneに設定できます。 真の場合、このフィールドは、生成された__hash__()メソッドに含まれます。 (デフォルトの)Noneの場合、compareの値を使います: こうすることは普通は期待通りの振る舞いになります。 比較で使われるフィールドはハッシュに含まれるものと考えるべきです。 この値をNone以外に設定することは推奨されません。フィールドのハッシュ値を計算するコストが高い場合に、
hash=Falseだがcompare=Trueと設定する理由が 1 つあるとすれば、フィールドが等価検査に必要かつ、その型のハッシュ値を計算するのに他のフィールドも使われることです。 フィールドがハッシュから除外されていたとしても、比較には使えます。metadata: これはマッピングあるいはNoneに設定できます。Noneは空の辞書として扱われます。 この値はMappingProxyType()でラップされ、読み出し専用になり、Fieldオブジェクトに公開されます。 これはデータクラスから使われることはなく、サードパーティーの拡張機構として提供されます。 複数のサードパーティーが各々のキーを持て、メタデータの名前空間として使えます。
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には値が設定されません。
-
class
dataclasses.Field¶ Fieldオブジェクトはそれぞれの定義されたフィールドを記述します。 このオブジェクトは内部で作られ、モジュールレベル関数のfields()によって返されます (下の解説を見てください)。 ユーザーは絶対にFieldオブジェクトを直接インスタンス化すべきではありません。 ドキュメント化されている属性は次の通りです:name: フィールド名type: フィールドの型default,default_factory,init,repr,hash,compare,metadataはfield()の宣言と同じ意味と値を持ちます。
他の属性があることもありますが、それらはプライベートであり、調べたり、依存したりしてはなりません。
-
dataclasses.fields(class_or_instance)¶ このデータクラスのフィールドを定義する
Fieldオブジェクトをタプルで返します。 データクラスあるいはデータクラスのインスタンスを受け付けます。 データクラスやデータクラスのインスタンスが渡されなかった場合は、TypeErrorを送出します。ClassVarやInitVarといった疑似フィールドは返しません。
-
dataclasses.asdict(obj, *, dict_factory=dict)¶ Converts the dataclass
objto a dict (by using the factory functiondict_factory). Each dataclass is converted to a dict of its fields, asname: valuepairs. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied withcopy.deepcopy().Example of using
asdict()on nested dataclasses:@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}]}
To create a shallow copy, the following workaround may be used:
dict((field.name, getattr(obj, field.name)) for field in fields(obj))
asdict()raisesTypeErrorifobjis not a dataclass instance.
-
dataclasses.astuple(obj, *, tuple_factory=tuple)¶ Converts the dataclass
objto a tuple (by using the factory functiontuple_factory). Each dataclass is converted to a tuple of its field values. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied withcopy.deepcopy().1つ前の例の続きです:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
To create a shallow copy, the following workaround may be used:
tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))
astuple()raisesTypeErrorifobjis not a dataclass instance.
-
dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)¶ cls_nameという名前、fieldsで定義されるフィールド、basesで与えられた基底クラス、namespaceで与えられた名前空間付きで初期化されたデータクラスを作成します。fieldsはイテラブルで、要素がname,(name, type),(name, type, Field)のうちのどれかです。 単にnameだけが与えられた場合は、typing.Anyがtypeとして使われます。init,repr,eq,order,unsafe_hash,frozenの値は、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
-
dataclasses.replace(obj, /, **changes)¶ Creates a new object of the same type as
obj, replacing fields with values fromchanges. Ifobjis not a Data Class, raisesTypeError. If values inchangesdo not specify fields, raisesTypeError.新しく返されるオブジェクトは、データクラスの
__init__()メソッドを呼び出して作成されます。 これにより、もしあれば__post_init__()も呼び出されることが保証されます。初期化限定変数でデフォルト値を持たないものがもしあれば、
replace()の呼び出し時に初期値が指定され、__init__()と__post_init__()に渡せるようにしなければなりません。changesに、init=Falseと定義されたフィールドが含まれているとエラーになります。 この場合ValueErrorが送出されます。replace()を呼び出しているときにinit=Falseであるフィールドがどのように働くかに気を付けてください。 そのフィールドは元のオブジェクトからコピーされるのではなく、仮に初期化されたとしても結局は__post_init__()で初期化されます。init=Falseであるフィールドは滅多に使いませんし、使うとしたら注意深く使用します。 そのようなフィールドが使われている場合は、代わりのクラスコンストラクタ、あるいは、インスタンスのコピー処理をする独自実装の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)
初期化後の処理¶
生成された __init__() のコードは、 __post_init__() という名前のメソッドがクラスに定義されていたら、それを呼び出します。
通常は self.__post_init__() のように呼び出されます。
しかし InitVar フィールドが定義されていた場合、それらもクラスに定義された順序で __post_init__() に渡されます。
__init__() メソッドが生成されなかった場合は、 __post_init__() は自動的には呼び出されません。
他の機能と組み合わせることで、他の 1 つ以上のフィールドに依存しているフィールドが初期化できます。 例えば次のようにできます:
@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__() メソッドを呼びません。もし、ベースクラスが :meth: __init__ メソッドを持ち、それを呼ぶ必要があれば、 __post_init__() メソッドの中から呼び出します:
@dataclass
class Rectangle:
height: float
width: float
@dataclass
class Square(Rectangle):
side: float
def __post_init__(self):
super().__init__(self.side, self.side)
派生データクラスが、データクラス自体である基本クラスのすべてのフィールドの初期化を処理するため、データクラスで生成された __init__() メソッドを呼び出す必要はありません。
下にある初期化限定変数についての節で、 __post_init__() にパラメータを渡す方法を参照してください。
replace() が init=False であるフィールドをどう取り扱うかについての警告も参照してください。
クラス変数¶
dataclass() が実際にフィールドの型の検査を行う 2 箇所のうち 1 つは、フィールドが PEP 526 で定義されたクラス変数かどうかの判定です。
その判定はフィールドの型が typing.ClassVar かどうかで行います。
フィールドが ClassVar の場合、フィールドとは見なされなくなり、データクラスの機構からは無視されます。
そのような ClassVar 疑似フィールドは、モジュールレベル関数 fields() の返り値には含まれません。
初期化限定変数¶
dataclass() が型アノテーションの検査を行うもう 1 つの箇所は、フィールドが初期化限定変数かどうかの判定です。
その判定はフィールドの型が dataclasses.InitVar 型であるかどうかで行います。
フィールドが InitVar の場合、初期化限定フィールドと呼ばれる疑似フィールドと見なされます。
これは本物のフィールドではないので、モジュールレベル関数 fields() の返り値には含まれません。
初期化限定フィールドは生成された __init__() メソッドに引数として追加され、オプションの __post_init__() メソッドにも渡されます。
初期化限定フィールドは、データクラスからはそれ以外では使われません。
例えば、あるフィールドがデータベースから初期化されると仮定して、クラスを作成するときには値が与えられない次の場合を考えます:
@dataclass
class C:
i: int
j: int = None
database: InitVar[DatabaseType] = 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 の Field オブジェクトは返しません。
凍結されたインスタンス¶
真に不変な Python のオブジェクトを作成するのは不可能です。
しかし、 frozen=True を dataclass() デコレータに渡すことで、不変性の模倣はできます。
このケースでは、データクラスは __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):
デフォルトファクトリ関数¶
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 = [] def add(self, element): self.x += elementデータクラスは次のようなコードを生成するでしょう:
class D: x = [] def __init__(self, x=x): self.x = x def add(self, element): self.x += element assert D().x is D().xこれには、クラス
Cを使ったさっきの例と同じ問題があります。 すなわち、クラスDの 2 つのインスタンスは、クラスインスタンスを作成するときにxの具体的な値を指定しておらず、同じxのコピーを共有します。 データクラスは Python の通常のクラス作成の仕組みを使っているだけなので、この同じ問題を抱えています。 データクラスがこの問題を検出する一般的な方法を持たない代わりに、データクラスは型がlistやdictやsetのデフォルトパラメーターを検出した場合、TypeErrorを送出します。 これは完全ではない解決法ですが、よくあるエラーの多くを防げます。デフォルトファクトリ関数を使うのが、フィールドのデフォルト値として可変な型の新しいインスタンスを作成する手段です:
@dataclass class D: x: list = field(default_factory=list) assert D().x is not D().x
例外¶
-
exception
dataclasses.FrozenInstanceError¶ frozen=True付きで定義されたデータクラスで、暗黙的に定義された__setattr__()または__delattr__()が呼び出されたときに送出されます。これはAttributeErrorのサブクラスです。