Sposób, w jaki klasy danych łączy atrybuty, uniemożliwia użycie atrybutów z wartościami domyślnymi w klasie bazowej, a następnie użycie atrybutów bez wartości domyślnych (atrybuty pozycyjne) w podklasie.
Dzieje się tak, ponieważ atrybuty są łączone, zaczynając od dołu MRO i tworząc uporządkowaną listę atrybutów w kolejności od pierwszego zobaczenia; nadpisania są zachowywane w ich pierwotnej lokalizacji. ParentZaczyna się więc od ['name', 'age', 'ugly'], gdzie uglyma wartość domyślną, a następnie Childdodaje ['school']na końcu tej listy (z uglyjuż na liście). Oznacza to, że kończysz z, ['name', 'age', 'ugly', 'school']a ponieważ schoolnie ma wartości domyślnej, skutkuje to nieprawidłowym wykazem argumentów dla __init__.
Jest to udokumentowane w klasach danych PEP-557 , w ramach dziedziczenia :
Gdy klasa danych jest tworzona przez @dataclassdekorator, przegląda wszystkie klasy bazowe klasy w odwrotnym MRO (czyli zaczynając od object) i dla każdej znalezionej klasy danych dodaje pola z tej klasy bazowej do uporządkowanej mapowanie pól. Po dodaniu wszystkich pól klasy bazowej dodaje własne pola do uporządkowanego mapowania. Wszystkie wygenerowane metody wykorzystają to połączone, obliczone uporządkowane mapowanie pól. Ponieważ pola są w kolejności wstawiania, klasy pochodne zastępują klasy podstawowe.
i pod specyfikacją :
TypeErrorzostanie podniesiony, jeśli pole bez wartości domyślnej następuje po polu z wartością domyślną. Dzieje się tak, gdy dzieje się to w jednej klasie lub w wyniku dziedziczenia klas.
Masz tutaj kilka opcji, aby uniknąć tego problemu.
Pierwszą opcją jest użycie oddzielnych klas bazowych, aby wymusić umieszczenie pól z wartościami domyślnymi na późniejszej pozycji w kolejności MRO. Za wszelką cenę unikaj ustawiania pól bezpośrednio w klasach, które mają być używane jako klasy bazowe, takich jak Parent.
Działa następująca hierarchia klas:
@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
Wyciągając pola do oddzielnych klas bazowych z polami bez wartości domyślnych i polami z wartościami domyślnymi oraz starannie dobraną kolejnością dziedziczenia, można utworzyć MRO, które umieszcza wszystkie pola bez wartości domyślnych przed polami z wartościami domyślnymi. Odwrócona MRO (ignorująca object) dla Childto:
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
Zwróć uwagę, że Parentnie ustawia to żadnych nowych pól, więc nie ma znaczenia, że kończy się jako ostatnie w kolejności wyświetlania pól. Klasy z polami bez wartości domyślnych ( _ParentBasei _ChildBase) poprzedzają klasy z polami z wartościami domyślnymi ( _ParentDefaultsBasei _ChildDefaultsBase).
Rezultatem jest Parenti Childklasy z rozsądnym polem starsze, podczas gdy Childnadal jest podklasą 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
więc możesz tworzyć instancje obu klas:
>>> 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)
Inną opcją jest używanie tylko pól z wartościami domyślnymi; nadal możesz popełnić błąd, aby nie podać schoolwartości, podnosząc ją w __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'")
ale nie zmieniają kolejność pól; schoolkończy się po ugly:
<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
a narzędzie do sprawdzania podpowiedzi typu będzie narzekać, że _no_defaultnie jest ciągiem znaków.
Możesz także skorzystać z attrsprojektu , który był projektem, który zainspirował dataclasses. Używa innej strategii łączenia dziedziczenia; ciągnie pola przesłonięte w podklasie na końcu listy pól, więc ['name', 'age', 'ugly']w Parentklasie staje się ['name', 'age', 'school', 'ugly']w Childklasie; nadpisując pole wartością domyślną, attrspozwala na nadpisanie bez konieczności wykonywania tańca MRO.
attrsobsługuje definiowanie pól bez podpowiedzi typu, ale pozwala trzymać się obsługiwanego trybu podpowiedzi typu poprzez ustawienie 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 :)