データクラスが属性を組み合わせる方法により、基本クラスでデフォルトのある属性を使用してから、サブクラスでデフォルトのない属性(位置属性)を使用することができなくなります。
これは、MROの下部から開始し、最初に表示された順序で属性の順序付きリストを作成することにより、属性が結合されるためです。オーバーライドは元の場所に保持されます。そうParent
で始まり['name', 'age', 'ugly']
どこ、ugly
デフォルトを持って、その後、Child
追加します['school']
(とそのリストの末尾にugly
リスト内にすでに存在)。これは、最終的にはデフォルトがない['name', 'age', 'ugly', 'school']
ためschool
、の引数リストが無効になることを意味します__init__
。
これは、継承の下で、PEP-557データクラスに文書化されています。
@dataclass
デコレータによってデータクラスが作成されている場合、デコレータはクラスのすべての基本クラスを逆MRO(つまり、から開始object
)で調べ、見つかったデータクラスごとに、その基本クラスのフィールドを順序付きに追加します。フィールドのマッピング。すべての基本クラスフィールドが追加された後、順序付けられたマッピングに独自のフィールドが追加されます。生成されたすべてのメソッドは、この結合され、計算されたフィールドの順序付きマッピングを使用します。フィールドは挿入順であるため、派生クラスは基本クラスをオーバーライドします。
および仕様の下で:
TypeError
デフォルト値のないフィールドがデフォルト値のあるフィールドの後に続く場合に発生します。これは、これが単一のクラスで発生する場合、またはクラスの継承の結果として発生する場合に当てはまります。
この問題を回避するために、ここにはいくつかのオプションがあります。
最初のオプションは、個別の基本クラスを使用して、デフォルトのフィールドをMRO順序の後半の位置に強制することです。とにかく、などの基本クラスとして使用されるクラスに直接フィールドを設定することは避けてくださいParent
。
次のクラス階層が機能します。
@dataclass
class _ParentBase:
name: str
age: int
@dataclass
class _ParentDefaultsBase:
ugly: bool = False
@dataclass
class _ChildBase(_ParentBase):
school: str
@dataclass
class _ChildDefaultsBase(_ParentDefaultsBase):
ugly: bool = True
@dataclass
class Parent(_ParentDefaultsBase, _ParentBase):
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f"The Name is {self.name} and {self.name} is {self.age} year old")
@dataclass
class Child(Parent, _ChildDefaultsBase, _ChildBase):
pass
デフォルトのないフィールドとデフォルトのあるフィールド、および慎重に選択された継承順序を持つ別々の基本クラスにフィールドを引き出すことにより、デフォルトのないすべてのフィールドをデフォルトのあるフィールドの前に置くMROを作成できます。の逆MRO(無視object
)Child
は次のとおりです。
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
Parent
新しいフィールドは設定されないため、ここではフィールドリストの順序で「最後」になることは問題ではないことに注意してください。デフォルト(_ParentBase
および_ChildBase
)のないフィールドを持つクラスは、デフォルト(_ParentDefaultsBase
および_ChildDefaultsBase
)のあるフィールドを持つクラスの前にあります。
結果はParent
とChild
しながら、古い正気のフィールドを持つクラスChild
はまだのサブクラスでありますParent
:
>>> from inspect import signature
>>> signature(Parent)
<Signature (name: str, age: int, ugly: bool = False) -> None>
>>> signature(Child)
<Signature (name: str, age: int, school: str, ugly: bool = True) -> None>
>>> issubclass(Child, Parent)
True
したがって、両方のクラスのインスタンスを作成できます。
>>> jack = Parent('jack snr', 32, ugly=True)
>>> jack_son = Child('jack jnr', 12, school='havard', ugly=True)
>>> jack
Parent(name='jack snr', age=32, ugly=True)
>>> jack_son
Child(name='jack jnr', age=12, school='havard', ugly=True)
もう1つのオプションは、デフォルトのフィールドのみを使用することです。次のschool
値を上げることで、値を指定しないというエラーを犯す可能性があります__post_init__
。
_no_default = object()
@dataclass
class Child(Parent):
school: str = _no_default
ugly: bool = True
def __post_init__(self):
if self.school is _no_default:
raise TypeError("__init__ missing 1 required argument: 'school'")
しかし、これはフィールドの順序を変更します。school
後に終わるugly
:
<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
タイプヒントチェッカーは_no_default
、文字列ではないと文句を言います。
また、使用することができますattrs
プロジェクト触発プロジェクトでした、dataclasses
。異なる継承マージ戦略を使用します。それはそう、フィールドリストの最後にサブクラスでオーバーライドされたフィールドを引く['name', 'age', 'ugly']
にはParent
、クラスとなっ['name', 'age', 'school', 'ugly']
にChild
クラス。デフォルトでフィールドをオーバーライドするattrs
ことにより、MROダンスを行う必要なしにオーバーライドを許可します。
attrs
タイプヒントなしでフィールドを定義することをサポートしますが、以下を設定することにより、サポートされているタイプヒントモードに固執することができますauto_attribs=True
。
import attr
@attr.s(auto_attribs=True)
class Parent:
name: str
age: int
ugly: bool = False
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f"The Name is {self.name} and {self.name} is {self.age} year old")
@attr.s(auto_attribs=True)
class Child(Parent):
school: str
ugly: bool = True
ugly: bool = True
= rekt :)