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
(instance, *, dict_factory=dict)¶ データクラスの
instance
を (ファクトリ関数dict_factory
を使い) 辞書に変換します。 それぞれのデータクラスは、name: value
という組になっている、フィールドの辞書に変換されます。 データクラス、辞書、リスト、タプルは再帰的に処理されます。 例えば、次のようになります:@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}]}
instance
がデータクラスのインスタンスでなかった場合、TypeError
を送出します。
-
dataclasses.
astuple
(instance, *, tuple_factory=tuple)¶ データクラスの
instance
を (ファクトリ関数tuple_factory
を使い) タプルに変換します。 それぞれのデータクラスは、フィールドの値のタプルに変換されます。 データクラス、辞書、リスト、タプルは再帰的に処理されます。1つ前の例の続きです:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
instance
がデータクラスのインスタンスでなかった場合、TypeError
を送出します。
-
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
(instance, **changes)¶ instance
と同じ型のオブジェクトを新しく作成し、フィールドをchanges
にある値で置き換えます。instance
がデータクラスではなかった場合、TypeError
を送出します。changes
にある値がフィールドを指定していなかった場合も、TypeError
を送出します。新しく返されるオブジェクトは、データクラスの
__init__()
メソッドを呼び出して作成されます。 これにより、もしあれば__post_init__()
も呼び出されることが保証されます。初期化限定変数でデフォルト値を持たないものがもしあれば、
replace()
の呼び出し時に初期値が指定され、__init__()
と__post_init__()
に渡せるようにしなければなりません。changes
に、init=False
と定義されたフィールドが含まれているとエラーになります。 この場合ValueError
が送出されます。replace()
を呼び出しているときにinit=False
であるフィールドがどのように働くかに気を付けてください。 そのフィールドは元のオブジェクトからコピーされるのではなく、仮に初期化されたとしても結局は__post_init__()
で初期化されます。init=False
であるフィールドは滅多に使いませんし、使うとしたら注意深く使用します。 そのようなフィールドが使われている場合は、代わりのクラスコンストラクタ、あるいは、インスタンスのコピー処理をする独自実装のreplace()
(もしくは似た名前の) メソッドを持たせるのが賢明でしょう。
-
dataclasses.
is_dataclass
(class_or_instance)¶ 引数がデータクラスかデータクラスのインスタンスだった場合に
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
下にある初期化限定変数についての節で、 __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__()
が呼び出されたときに送出されます。