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 Parent
beginnt mit ['name', 'age', 'ugly']
, wo ugly
eine Standard hat, und dann Child
fügt ['school']
an das Ende der Liste (mit ugly
bereits in der Liste). Dies bedeutet, dass Sie am Ende eine ungültige Argumentliste für haben, ['name', 'age', 'ugly', 'school']
da school
dies keine Standardeinstellung hat __init__
.
Dies ist in PEP-557- Datenklassen unter Vererbung dokumentiert :
Wenn die Datenklasse vom @dataclass
Dekorateur 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 :
TypeError
wird 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 Child
ist:
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
Beachten Sie, dass Parent
keine 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 ( _ParentBase
und _ChildBase
) stehen vor den Klassen mit Feldern mit Standardwerten ( _ParentDefaultsBase
und _ChildDefaultsBase
).
Das Ergebnis ist Parent
und Child
Klassen mit einem vernünftigen Feld älter, während Child
noch 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 school
Wert 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; school
endet 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_default
kein String sein.
Sie können auch das attrs
Projekt 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 Parent
Klasse wird ['name', 'age', 'school', 'ugly']
in der Child
Klasse; Durch Überschreiben des Felds mit einer Standardeinstellung wird attrs
das Überschreiben ermöglicht, ohne dass ein MRO-Tanz ausgeführt werden muss.
attrs
Unterstü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