dataclasses --- データクラス

ソースコード: Lib/dataclasses.py


このモジュールは、__init__()__repr__() のような special method を生成し、ユーザー定義のクラスに自動的に追加するデコレータや関数を提供します。このモジュールは PEP 557 に記載されました。

これらの生成されたメソッドで利用されるメンバー変数は PEP 526 型アノテーションを用いて定義されます。例えば、このコードでは:

@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`` の場合、 eqfrozen がどう設定されているかに従って __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 になります。

    eqfrozen が両方とも真だった場合、デフォルトでは dataclass()__hash__() メソッドを生成します。 eq が真で frozen が偽の場合、__hash__()None に設定され、(可変なので) ハッシュ化不可能とされます。 eq が偽の場合は、 __hash__() は手を付けないまま、つまりスーパークラスの __hash__() メソッドが使われることになります (スーパークラスが object だった場合は、 id に基づいたハッシュ化にフォールバックするということになります)。

  • frozen: 真 (デフォルト値は偽) の場合、フィールドへの代入は例外を生成します。 これにより読み出し専用の凍結されたインスタンスを模倣します。 __setattr__() あるいは __delattr__() がクラスに定義されていた場合は、 TypeError が送出されます。 後にある議論を参照してください。

フィールド には、通常の Python の文法でデフォルト値を指定できます。

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

この例では、生成された __init__() メソッドには ab の両方が含まれ、以下のように定義されます:

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 パラメータが提供されたかどうかを検出するのに使われる番兵オブジェクトです。 この番兵が使われるのは、 Nonedefault の有効な値だからです。 どんなコードでも MISSING 値を直接使うべきではありません。

field() の引数は次の通りです:

  • default: 与えられた場合、このフィールドのデフォルト値になります。 これが必要なのは、 field() の呼び出しそのものが通常ではデフォルト値がいる位置を横取りしているからです。

  • default_factory: 提供されていた場合、0 引数の呼び出し可能オブジェクトでなければならず、このフィールドの初期値が必要になったときに呼び出されます。 他の目的も含めて、下で議論されているように、フィールドに可変なデフォルト値を指定するのに使えます。 defaultdefault_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.z10 、クラス属性 C.t20 になり、クラス属性 C.xC.y には値が設定されません。

class dataclasses.Field

Field オブジェクトはそれぞれの定義されたフィールドを記述します。 このオブジェクトは内部で作られ、モジュールレベル関数の fields() によって返されます (下の解説を見てください)。 ユーザーは絶対に Field オブジェクトを直接インスタンス化すべきではありません。 ドキュメント化されている属性は次の通りです:

  • name: フィールド名
  • type: フィールドの型
  • default, default_factory, init, repr, hash, compare, metadatafield() の宣言と同じ意味と値を持ちます。

他の属性があることもありますが、それらはプライベートであり、調べたり、依存したりしてはなりません。

dataclasses.fields(class_or_instance)

このデータクラスのフィールドを定義する Field オブジェクトをタプルで返します。 データクラスあるいはデータクラスのインスタンスを受け付けます。 データクラスやデータクラスのインスタンスが渡されなかった場合は、 TypeError を送出します。 ClassVarInitVar といった疑似フィールドは返しません。

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.Anytype として使われます。 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()ijField オブジェクトは返しますが、 databaseField オブジェクトは返しません。

凍結されたインスタンス

真に不変な Python のオブジェクトを作成するのは不可能です。 しかし、 frozen=Truedataclass() デコレータに渡すことで、不変性の模倣はできます。 このケースでは、データクラスは __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 の通常のクラス作成の仕組みを使っているだけなので、この同じ問題を抱えています。 データクラスがこの問題を検出する一般的な方法を持たない代わりに、データクラスは型が listdictset のデフォルトパラメーターを検出した場合、 TypeError を送出します。 これは完全ではない解決法ですが、よくあるエラーの多くを防げます。

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

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

例外

exception dataclasses.FrozenInstanceError

frozen=True 付きで定義されたデータクラスで、暗黙的に定義された __setattr__() または __delattr__() が呼び出されたときに送出されます。