dataclasses —データクラス—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/library/dataclasses
移動先:案内検索

dataclasses —データクラス

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



このモジュールは、__init__()__repr__()などの生成された特殊メソッドをユーザー定義クラスに自動的に追加するためのデコレータと関数を提供します。 もともとは 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)

この関数は、以下に説明するように、生成された特殊メソッドをクラスに追加するために使用されるデコレータです。

dataclass()デコレータは、クラスを調べてfieldを見つけます。 fieldは、型アノテーションを持つクラス変数として定義されています。 以下で説明する2つの例外を除いて、 dataclass()には、変数アノテーションで指定された型を検査するものはありません。

生成されたすべてのメソッドのフィールドの順序は、クラス定義に表示される順序です。

dataclass()デコレータは、以下で説明するように、さまざまな「dunder」メソッドをクラスに追加します。 追加されたメソッドのいずれかがクラスにすでに存在する場合、以下に説明するように、動作はパラメーターによって異なります。 デコレータは、呼び出されたのと同じクラスを返します。 新しいクラスは作成されません。

dataclass()がパラメーターのない単純なデコレーターとして使用される場合、この署名に記載されているデフォルト値があるかのように機能します。 つまり、 dataclass()の次の3つの使用法は同等です。

@dataclass
class C:
    ...

@dataclass()
class C:
    ...

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
   ...

dataclass()のパラメーターは次のとおりです。

  • init:true(デフォルト)の場合、__init__()メソッドが生成されます。

    クラスがすでに__init__()を定義している場合、このパラメーターは無視されます。

  • repr:true(デフォルト)の場合、__repr__()メソッドが生成されます。 生成されたrepr文字列には、クラスで定義されている順序で、クラス名と各フィールドの名前とreprが含まれます。 reprから除外されているとマークされているフィールドは含まれません。 例:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)

    クラスがすでに__repr__()を定義している場合、このパラメーターは無視されます。

  • eq:true(デフォルト)の場合、__eq__()メソッドが生成されます。 このメソッドは、クラスをそのフィールドのタプルであるかのように順番に比較します。 比較の両方のインスタンスは同じタイプである必要があります。

    クラスがすでに__eq__()を定義している場合、このパラメーターは無視されます。

  • order:trueの場合(デフォルトはFalse)、__lt__()__le__()__gt__()、および__ge__()メソッド生成されます。 これらは、クラスをそのフィールドのタプルであるかのように順番に比較します。 比較の両方のインスタンスは同じタイプである必要があります。 orderがtrueで、eqがfalseの場合、 ValueError が発生します。

    クラスがすでに__lt__()__le__()__gt__()、または__ge__()のいずれかを定義している場合、 TypeError が発生します。

  • unsafe_hashFalse(デフォルト)の場合、eqおよびfrozenの設定方法に従って、__hash__()メソッドが生成されます。

    __hash__()は、組み込みの hash()によって使用され、オブジェクトが辞書やセットなどのハッシュされたコレクションに追加されるときに使用されます。 __hash__()があるということは、クラスのインスタンスが不変であることを意味します。 可変性は、プログラマーの意図、__eq__()の存在と動作、および dataclass((X178X] dataclass( )デコレータ。

    デフォルトでは、 dataclass()は、安全でない限り、__hash__()メソッドを暗黙的に追加しません。 また、既存の明示的に定義された__hash__()メソッドを追加または変更することもありません。 __hash__()のドキュメントで説明されているように、クラス属性__hash__ = Noneの設定は、Pythonにとって特定の意味を持ちます。

    __hash__()が明示的に定義されていない場合、またはNoneに設定されている場合、 dataclass() may は暗黙的な__hash__()を追加します] 方法。 推奨されていませんが、 dataclass()unsafe_hash=Trueを使用して__hash__()メソッドを作成させることができます。 これは、クラスが論理的に不変であるにもかかわらず、変更できる場合に当てはまる可能性があります。 これは特殊なユースケースであり、慎重に検討する必要があります。

    __hash__()メソッドの暗黙的な作成を管理するルールは次のとおりです。 データクラスに明示的な__hash__()メソッドを含めることと、unsafe_hash=Trueを設定することの両方を行うことはできないことに注意してください。 これにより、 TypeError が発生します。

    eqfrozenの両方がtrueの場合、デフォルトで dataclass()__hash__()メソッドを生成します。 eqがtrueで、frozenがfalseの場合、__hash__()Noneに設定され、ハッシュ不可としてマークされます(つまり、可変であるため)。 eqがfalseの場合、__hash__()は変更されないままになり、スーパークラスの__hash__()メソッドが使用されます(スーパークラスがオブジェクトの場合、これはIDベースのハッシュにフォールバックします)。

  • frozen:trueの場合(デフォルトはFalse)、フィールドに割り当てると例外が生成されます。 これは、読み取り専用のフリーズされたインスタンスをエミュレートします。 __setattr__()または__delattr__()がクラスで定義されている場合、 TypeError が発生します。 以下の説明を参照してください。

fieldは、通常のPython構文を使用して、オプションでデフォルト値を指定できます。

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

この例では、abの両方が、追加された__init__()メソッドに含まれます。これは次のように定義されます。

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:指定されている場合、このフィールドにデフォルト値が必要なときに呼び出される引数なしの呼び出し可能である必要があります。 他の目的の中でも、これは、以下で説明するように、変更可能なデフォルト値を持つフィールドを指定するために使用できます。 defaultdefault_factoryの両方を指定するとエラーになります。

  • init:true(デフォルト)の場合、このフィールドは、生成された__init__()メソッドのパラメーターとして含まれます。

  • repr:true(デフォルト)の場合、このフィールドは、生成された__repr__()メソッドによって返される文字列に含まれます。

  • compare:true(デフォルト)の場合、このフィールドは、生成された等式および比較メソッド(__eq__()__gt__()など)に含まれます。

  • hash:これはブール値またはNoneの場合があります。 trueの場合、このフィールドは生成された__hash__()メソッドに含まれます。 None(デフォルト)の場合は、compareの値を使用します。これは通常予想される動作です。 フィールドを比較に使用する場合は、ハッシュでフィールドを考慮する必要があります。 この値をNone以外に設定することはお勧めしません。

    hash=Falseを設定する理由の1つとして、compare=Trueは、フィールドのハッシュ値の計算にコストがかかる場合、そのフィールドが等価性テストに必要である場合、およびタイプに寄与する他のフィールドがある場合が考えられます。ハッシュ値。 フィールドがハッシュから除外されている場合でも、比較に使用されます。

  • metadata:これはマッピングまたはなしの場合があります。 空のdictとして扱われるものはありません。 この値は 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.xおよびC.yは設定されません。

class dataclasses.Field

Field オブジェクトは、定義された各フィールドを記述します。 これらのオブジェクトは内部で作成され、 fields()モジュールレベルのメソッドによって返されます(以下を参照)。 ユーザーは、 Field オブジェクトを直接インスタンス化しないでください。 その文書化された属性は次のとおりです。

  • name:フィールドの名前。

  • type:フィールドのタイプ。

  • defaultdefault_factoryinitreprhashcompare、およびmetadataには field()宣言と同じ意味と値。


他の属性が存在する可能性がありますが、それらは非公開であり、検査または信頼してはなりません。

dataclasses.fields(class_or_instance)
このデータクラスのフィールドを定義する Field オブジェクトのタプルを返します。 データクラスまたはデータクラスのインスタンスのいずれかを受け入れます。 データクラスまたはそのインスタンスが渡されない場合、 TypeError を発生させます。 ClassVarまたはInitVarの疑似フィールドを返しません。
dataclasses.asdict(instance, *, dict_factory=dict)

データクラスinstanceをdictに変換します(ファクトリ関数dict_factoryを使用)。 各データクラスは、name: valueペアとして、そのフィールドのdictに変換されます。 データクラス、dict、リスト、およびタプルはに再帰されます。 例えば:

@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を使用)。 各データクラスは、そのフィールド値のタプルに変換されます。 データクラス、dict、リスト、およびタプルはに再帰されます。

前の例からの続き:

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のみを指定した場合、typeにはtyping.Anyが使用されます。 initrepreqorderunsafe_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__()がクラスで定義されている場合、__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__()にパラメーターを渡す方法については、initのみの変数に関する以下のセクションを参照してください。 replace()init=Falseフィールドを処理する方法に関する警告も参照してください。


クラス変数

dataclass()が実際にフィールドの型を検査する2つの場所の1つは、フィールドが PEP 526 で定義されているクラス変数であるかどうかを判別することです。 これは、フィールドのタイプがtyping.ClassVarであるかどうかを確認することによって行われます。 フィールドがClassVarの場合、フィールドとしての考慮から除外され、データクラスメカニズムによって無視されます。 このようなClassVar疑似フィールドは、モジュールレベルの fields()関数によって返されません。


初期化のみの変数

dataclass()が型注釈を検査するもう1つの場所は、フィールドがinitのみの変数であるかどうかを判別することです。 これは、フィールドのタイプがdataclasses.InitVarタイプであるかどうかを確認することによって行われます。 フィールドがInitVarの場合、init-onlyフィールドと呼ばれる疑似フィールドと見なされます。 これは真のフィールドではないため、モジュールレベルの 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に対しては返しません。


凍結されたインスタンス

真に不変のPythonオブジェクトを作成することはできません。 ただし、frozen=Truedataclass()デコレータに渡すことで、不変性をエミュレートできます。 その場合、データクラスは__setattr__()および__delattr__()メソッドをクラスに追加します。 これらのメソッドは、呼び出されると FrozenInstanceError を発生させます。

frozen=Trueを使用すると、パフォーマンスがわずかに低下します。__init__()は単純な割り当てを使用してフィールドを初期化できないため、 object .__ setattr __()を使用する必要があります。


継承

データクラスが dataclass()デコレータによって作成されている場合、逆MRO(つまり、オブジェクトから開始)でクラスのすべての基本クラスを調べ、データクラスごとに調べます。見つかったものは、その基本クラスのフィールドをフィールドの順序付けられたマッピングに追加します。 すべての基本クラスフィールドが追加された後、順序付けられたマッピングに独自のフィールドが追加されます。 生成されたすべてのメソッドは、フィールドのこの結合され、計算された順序付きマッピングを使用します。 フィールドは挿入順であるため、派生クラスは基本クラスをオーバーライドします。 例:

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

フィールドの最終的なリストは、順番にxyzです。 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__()から除外され(init=Falseを使用)、フィールドが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を共有していることに注意してください。

データクラスを使用して、 if このコードは有効でした。

@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を使用した元の例と同じ問題があります。 つまり、クラスインスタンスの作成時にxの値を指定しないクラスDの2つのインスタンスは、xの同じコピーを共有します。 データクラスは通常のPythonクラス作成を使用するだけなので、この動作も共有します。 データクラスがこの状態を検出する一般的な方法はありません。 代わりに、データクラスは、タイプlistdict、または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 のサブクラスです。