Die Art und Weise, wie Datenklassen Attribute kombinieren, verhindert, dass Sie Attribute mit Standardwerten in einer Basisklasse verwenden und dann Attribute ohne Standard (Positionsattribute) in einer Unterklasse verwenden können.
Dies liegt daran, dass die Attribute kombiniert werden, indem Sie am unteren Rand des MRO beginnen und eine geordnete Liste der Attribute in der zuerst gesehenen Reihenfolge erstellen. Überschreibungen werden an ihrem ursprünglichen Ort aufbewahrt. So Parentbeginnt mit ['name', 'age', 'ugly'], wo uglyeine Standard hat, und dann Childfügt ['school']an das Ende der Liste (mit uglybereits in der Liste). Dies bedeutet, dass Sie am Ende eine ungültige Argumentliste für haben, ['name', 'age', 'ugly', 'school']da schooldies keine Standardeinstellung hat __init__.
Dies ist in PEP-557- Datenklassen unter Vererbung dokumentiert :
Wenn die Datenklasse vom @dataclassDekorateur erstellt wird, durchsucht sie alle Basisklassen der Klasse in umgekehrter MRO (dh beginnend mit object) und fügt für jede gefundene Datenklasse die Felder dieser Basisklasse einer geordneten hinzu Zuordnung von Feldern. Nachdem alle Basisklassenfelder hinzugefügt wurden, werden der geordneten Zuordnung eigene Felder hinzugefügt. Alle generierten Methoden verwenden diese kombinierte, berechnete geordnete Zuordnung von Feldern. Da die Felder in der Einfügereihenfolge sind, überschreiben abgeleitete Klassen Basisklassen.
und unter Spezifikation :
TypeErrorwird ausgelöst, wenn ein Feld ohne Standardwert auf ein Feld mit einem Standardwert folgt. Dies gilt entweder, wenn dies in einer einzelnen Klasse auftritt oder als Ergebnis der Klassenvererbung.
Sie haben hier einige Optionen, um dieses Problem zu vermeiden.
Die erste Option besteht darin, separate Basisklassen zu verwenden, um Felder mit Standardwerten an eine spätere Position in der MRO-Reihenfolge zu zwingen. Vermeiden Sie es auf jeden Fall, Felder direkt für Klassen festzulegen, die als Basisklassen verwendet werden sollen, z Parent.
Die folgende Klassenhierarchie funktioniert:
@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
Durch Herausziehen von Feldern in separate Basisklassen mit Feldern ohne Standardwerte und Felder mit Standardwerten und einer sorgfältig ausgewählten Vererbungsreihenfolge können Sie eine MRO erstellen, bei der alle Felder ohne Standardwerte vor denen mit Standardwerten platziert werden. Die umgekehrte MRO (ignoriert object) für Childist:
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
Beachten Sie, dass Parentkeine neuen Felder festgelegt werden. Daher spielt es hier keine Rolle, dass es in der Reihenfolge der Feldlisten als "letztes" endet. Die Klassen mit Feldern ohne Standardwerte ( _ParentBaseund _ChildBase) stehen vor den Klassen mit Feldern mit Standardwerten ( _ParentDefaultsBaseund _ChildDefaultsBase).
Das Ergebnis ist Parentund ChildKlassen mit einem vernünftigen Feld älter, während Childnoch eine Unterklasse von 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
So können Sie Instanzen beider Klassen erstellen:
>>> 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)
Eine andere Möglichkeit besteht darin, nur Felder mit Standardwerten zu verwenden. Sie können immer noch den Fehler machen, keinen schoolWert anzugeben, indem Sie einen in erhöhen __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'")
aber dies tut die Feldreihenfolge ändern; schoolendet nach ugly:
<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
und eine Art Hinweis checker wird bemängelt _no_defaultkein String sein.
Sie können auch das attrsProjekt verwenden , das Sie inspiriert hat dataclasses. Es wird eine andere Strategie zum Zusammenführen von Vererbungen verwendet. es zieht überschriebene Felder in einer Unterklasse zum Ende der Liste Felder, so ['name', 'age', 'ugly']in der ParentKlasse wird ['name', 'age', 'school', 'ugly']in der ChildKlasse; Durch Überschreiben des Felds mit einer Standardeinstellung wird attrsdas Überschreiben ermöglicht, ohne dass ein MRO-Tanz ausgeführt werden muss.
attrsUnterstützt das Definieren von Feldern ohne Typhinweise, bleibt jedoch beim unterstützten Typhinweismodus, indem Sie Folgendes festlegen 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