私は現在、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
どうすれば修正できますか?
データクラスが属性を結合する方法により、基本クラスでデフォルトの属性を使用し、サブクラスでデフォルトのない属性(位置属性)を使用することができなくなります。
これは、MROの一番下から開始し、最初に表示された順序で属性の順序付きリストを作成することにより、属性が結合されるためです。オーバーライドは元の場所に保持されます。 Parent
は['name', 'age', 'ugly']
で始まり、ugly
にはデフォルトがあり、Child
はそのリストの最後に['school']
を追加します(ugly
は既にリストにあります)。これは、最終的に['name', 'age', 'ugly', 'school']
になり、school
にはデフォルトがないため、__init__
の引数リストが無効になることを意味します。
これは PEP-557Dataclasses で inheritance :
データクラスが
@dataclass
デコレータによって作成されるとき、逆MRO(つまり、object
で始まる)でクラスのすべての基本クラスを調べ、検出した各データクラスについて、その基本クラスのフィールドをフィールドの順序付けられたマッピングに追加します。すべての基本クラスフィールドが追加された後、独自のフィールドを順序付けられたマッピングに追加します。生成されたすべてのメソッドは、この結合され計算されたフィールドの順序マッピングを使用します。フィールドは挿入順であるため、派生クラスは基本クラスをオーバーライドします。
および Specification の下:
デフォルト値のないフィールドがデフォルト値のあるフィールドの後に続く場合、
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(Parent, _ChildDefaultsBase, _ChildBase):
pass
フィールドをseparateに引き抜くことにより、デフォルトのないフィールドとデフォルトのあるフィールド、および慎重に選択された継承順序を持つ基本クラス、すべてを置くMROを生成できますデフォルトのあるフィールドより前のデフォルトのないフィールド。 object
の逆MRO(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)
別のオプションは、デフォルトのフィールドのみを使用することです。 __post_init__
で値を上げることにより、school
値を提供しないようにエラーを発生させることができます。
_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'")
ただし、このdoesはフィールドの順序を変更します。 school
はugly
の後になります:
<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
そして、タイプヒントチェッカーwillは、_no_default
が文字列ではないことについて文句を言います。
attrs
project を使用することもできます。これはdataclasses
に影響を与えたプロジェクトです。別の継承マージ戦略を使用します。サブクラスのオーバーライドされたフィールドをフィールドリストの最後にプルするため、Parent
クラスの['name', 'age', 'ugly']
はChild
クラスの['name', 'age', 'school', 'ugly']
になります。フィールドをデフォルトでオーバーライドすることにより、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
デフォルト値のない引数がデフォルト値のある引数の後に追加されているため、このエラーが表示されます。継承されたフィールドのデータクラスへの挿入順序は Method Resolution Order の逆です。つまり、Parent
フィールドは、後で子によって上書きされた場合でも最初に来ることを意味します。
PEP-557-データクラス の例:
@dataclass class Base: x: Any = 15.0 y: int = 0 @dataclass class C(Base): z: int = 10 x: int = 15
フィールドの最後のリストは、順番に、
x, y, z
。x
の最終型は、クラスint
で指定されているC
です。
残念ながら、これを回避する方法はないと思います。私の理解では、親クラスにデフォルト引数がある場合、子クラスはデフォルト以外の引数を持つことはできません。
martijn Pietersソリューションに基づいて、次のことを行いました。
1)post_initを実装するミキシングを作成する
from dataclasses import dataclass
no_default = object()
@dataclass
class NoDefaultAttributesPostInitMixin:
def __post_init__(self):
for key, value in self.__dict__.items():
if value is no_default:
raise TypeError(
f"__init__ missing 1 required argument: '{key}'"
)
2)次に、継承の問題があるクラスで:
from src.utils import no_default, NoDefaultAttributesChild
@dataclass
class MyDataclass(DataclassWithDefaults, NoDefaultAttributesPostInitMixin):
attr1: str = no_default
以下のアプローチは、純粋なpython dataclasses
を使用し、多くの定型コードなしでこの問題を処理します。
_ugly_init: dataclasses.InitVar[bool]
_は pseudo-field として機能し、初期化を行うのに役立ちます。インスタンスが作成されると失われます。 ugly: bool = field(init=False)
はインスタンスメンバであり、___init__
_メソッドでは初期化されませんが、___post_init__
_メソッドを使用して初期化することもできます(詳細は here を参照してください)。 )。
_from dataclasses import dataclass, field
@dataclass
class Parent:
name: str
age: int
ugly: bool = field(init=False)
ugly_init: dataclasses.InitVar[bool]
def __post_init__(self, ugly_init: bool):
self.ugly = ugly_init
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
jack = Parent('jack snr', 32, ugly_init=True)
jack_son = Child('jack jnr', 12, school='havard', ugly_init=True)
jack.print_id()
jack_son.print_id()
_