私は現在、Python 3.7 で導入された新しいデータクラス構造を試しています。現在、親クラスの継承を試みていますが、行き詰まっています。現在のアプローチでは、引数の順序が間違っているようで、子クラスの bool パラメータが他のパラメータの前に渡されます。これにより、型エラーが発生します。
from dataclasses import dataclass
@dataclass
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')
@dataclass
class Child(Parent):
school: str
ugly: bool = True
jack = Parent('jack snr', 32, ugly=True)
jack_son = Child('jack jnr', 12, school = 'havard', ugly=True)
jack.print_id()
jack_son.print_id()
このコードを実行すると、次のようになりますTypeError
:
TypeError: non-default argument 'school' follows default argument
これを修正するにはどうすればいいでしょうか?
ベストアンサー1
データクラスが属性を組み合わせる方法により、基本クラスでデフォルトを持つ属性を使用し、サブクラスでデフォルトを持たない属性 (位置属性) を使用することができなくなります。
これは、属性が MRO の一番下から始めて最初に出現した順序で属性の順序付きリストを構築することによって結合されるためです。オーバーライドは元の場所に保持されます。つまり、 は ( はデフォルトを持つ)Parent
で始まり、次に はそのリストの末尾に追加します( はすでにリスト内にあります)。つまり、 となり、 にはデフォルトがないため、 の無効な引数リストになります。['name', 'age', 'ugly']
ugly
Child
['school']
ugly
['name', 'age', 'ugly', 'school']
school
__init__
これは文書化されているPEP-557データクラス、 下継承:
データ クラスがデコレータによって作成されるとき
@dataclass
、デコレータはクラスのすべての基本クラスを逆 MRO (つまり、 から開始object
) で調べ、見つかったデータ クラスごとに、その基本クラスのフィールドをフィールドの順序付きマッピングに追加します。すべての基本クラスのフィールドが追加された後、データ クラスはそれ自身のフィールドを順序付きマッピングに追加します。生成されたすべてのメソッドは、この結合され計算されたフィールドの順序付きマッピングを使用します。フィールドは挿入順になっているため、派生クラスは基本クラスをオーバーライドします。
そして以下仕様:
TypeError
デフォルト値のないフィールドの後にデフォルト値のあるフィールドが続くと、例外が発生します。これは、単一のクラスで発生した場合、またはクラス継承の結果として発生した場合のいずれの場合にも当てはまります。
この問題を回避するには、いくつかのオプションがあります。
最初のオプションは、別の基本クラスを使用して、デフォルトを持つフィールドを MRO 順序の後の位置に強制することです。 など、基本クラスとして使用されるクラスに直接フィールドを設定することは絶対に避けてくださいParent
。
次のクラス階層が機能します。
# base classes with fields; fields without defaults separate from fields with.
@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
# public classes, deriving from base-with, base-without field classes
# subclasses of public classes should put the public base class up front.
@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(_ChildDefaultsBase, Parent, _ChildBase):
pass
デフォルトのないフィールドとデフォルトのあるフィールドを持つ別々の基本クラスにフィールドを取り出し、継承順序を慎重に選択することで、デフォルトのないすべてのフィールドをデフォルトのあるフィールドの前に置く MRO を生成できます。 の逆の MRO ( を無視object
) は次のようにChild
なります。
_ParentBase
_ChildBase
_ParentDefaultsBase
Parent
_ChildDefaultsBase
Parent
は新しいフィールドを設定しませんが、 からフィールドを継承し_ParentDefaultsBase
、フィールドのリスト順序で が「最後」にならないことに注意してください。 上記の順序では が最後になるため、そのフィールドが「勝ちます」。 データクラスのルールも満たされています。 デフォルトのないフィールドを持つクラス (および)_ChildDefaultsBase
は、デフォルトのフィールドを持つクラス (および) よりも前になります。_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
で 1 つを発生させることで、値を指定しないとエラーが発生するようにすることができます__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
なります。フィールドをデフォルトでオーバーライドすることで、MRO ダンスを行わなくてもオーバーライドが可能になります。['name', 'age', 'school', 'ugly']
Child
attrs
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