Recabamos por distintos espacios para así darte la solución para tu problema, si continúas con dificultades puedes dejarnos tu inquietud y responderemos con gusto.
Solución:
La forma en que se combinan las clases de datos attributes le impide poder usar attributes con valores predeterminados en una clase base y luego use attributes sin un valor predeterminado (posicional attributes) en una subclase.
Eso es porque el attributes se combinan comenzando desde la parte inferior del MRO y construyendo una lista ordenada de los attributes en orden de primera vista; las anulaciones se mantienen en su ubicación original. Entonces Parent
comienza con ['name', 'age', 'ugly']
, dónde ugly
tiene un valor predeterminado, y luego Child
agrega ['school']
al final de esa lista (con ugly
ya en la lista). Esto significa que terminas con ['name', 'age', 'ugly', 'school']
y porqué school
no tiene un valor predeterminado, esto da como resultado una lista de argumentos no válidos para __init__
.
Esto está documentado en PEP-557 Clases de datos, debajo herencia:
Cuando la clase de datos está siendo creada por el
@dataclass
decorador, mira a través de todas las clases base de la clase en MRO inverso (es decir, comenzando enobject
) y, para cada clase de datos que encuentra, agrega los campos de esa clase base a un mapeo ordenado de campos. Después de agregar todos los campos de la clase base, agrega sus propios campos al mapeo ordenado. Todos los métodos generados utilizarán este mapeo ordenado combinado y calculado de campos. Debido a que los campos están en orden de inserción, las clases derivadas anulan las clases base.
Y debajo Especificación:
TypeError
se generará si un campo sin un valor predeterminado sigue a un campo con un valor predeterminado. Este es true ya sea cuando esto ocurre en una sola clase o como resultado de la herencia de clases.
Tiene algunas opciones aquí para evitar este problema.
La primera opción es usar clases base separadas para forzar los campos con valores predeterminados a una posición posterior en la orden de MRO. A toda costa, evite establecer campos directamente en clases que se utilizarán como clases base, como Parent
.
La siguiente jerarquía de clases funciona:
# 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
Sacando campos hacia separar clases base con campos sin valores predeterminados y campos con valores predeterminados, y un orden de herencia cuidadosamente seleccionado, puede producir un MRO que coloca todos los campos sin valores predeterminados antes que aquellos con valores predeterminados. El MRO invertido (ignorando object
) por Child
es:
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
Tenga en cuenta que Parent
no establece ningún campo nuevo, por lo que no importa aquí que termine en el “último” en el orden de lista de campos. Las clases con campos sin valores predeterminados (_ParentBase
y _ChildBase
) preceden a las clases con campos con valores predeterminados (_ParentDefaultsBase
y _ChildDefaultsBase
).
El resultado es Parent
y Child
clases con un campo sano mayor, mientras que Child
sigue siendo una subclase de Parent
:
>>> from inspect import signature
>>> signature(Parent)
None>
>>> signature(Child)
None>
>>> issubclass(Child, Parent)
True
y así puedes crear instancias de ambas clases:
>>> 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)
Otra opción es usar solo campos con valores predeterminados; todavía puede cometer un error para no proporcionar un school
valor, elevando uno en __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'")
pero esto lo hace alterar el orden de los campos; school
termina después de ugly
:
) -> None>
y un verificador de sugerencias de tipo voluntad quejarse _no_default
no ser un string.
También puede utilizar el attrs
proyecto, que fue el proyecto que inspiró dataclasses
. Utiliza una estrategia de fusión de herencia diferente; extrae los campos anulados en una subclase al final de la lista de campos, por lo que ['name', 'age', 'ugly']
en el Parent
la clase se convierte en ['name', 'age', 'school', 'ugly']
en el Child
clase; anulando el campo con un valor predeterminado, attrs
permite la anulación sin necesidad de hacer un baile MRO.
attrs
admite la definición de campos sin sugerencias de tipo, pero sigamos con el modo de sugerencia de tipo admitido configurando 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
Está viendo este error porque se agrega un argumento sin un valor predeterminado después de un argumento con un valor predeterminado. El orden de inserción de los campos heredados en la clase de datos es el inverso del Orden de resolución del método, lo que significa que el Parent
los campos vienen primero, incluso si sus hijos los sobrescriben más tarde.
Un ejemplo de PEP-557 – Clases de datos:
@dataclass class Base: x: Any = 15.0 y: int = 0 @dataclass class C(Base): z: int = 10 x: int = 15
La lista final de campos es, en orden,
x, y, z
. El tipo final dex
esint
, como se especifica en la claseC
.
Desafortunadamente, no creo que haya ninguna forma de evitar esto. Tengo entendido que si la clase principal tiene un argumento predeterminado, ninguna clase secundaria puede tener argumentos no predeterminados.
El siguiente enfoque trata este problema mientras se usa Python puro dataclasses
y sin mucho código repetitivo.
los ugly_init: dataclasses.InitVar[bool]
sirve como un pseudocampo solo para ayudarnos a realizar la inicialización y se perderá una vez que se cree la instancia. Tiempo ugly: bool = field(init=False)
es un miembro de instancia que no será inicializado por __init__
método pero se puede inicializar alternativamente usando __post_init__
método (puede encontrar más aquí).
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()
Si desea utilizar un patrón donde ugly_init
es opcional, puede definir un método de clase en el Padre que incluya ugly_init
como parámetro opcional:
from dataclasses import dataclass, field, InitVar
@dataclass
class Parent:
name: str
age: int
ugly: bool = field(init=False)
ugly_init: InitVar[bool]
def __post_init__(self, ugly_init: bool):
self.ugly = ugly_init
@classmethod
def create(cls, ugly_init=True, **kwargs):
return cls(ugly_init=ugly_init, **kwargs)
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.create(name='jack snr', age=32, ugly_init=False)
jack_son = Child.create(name='jack jnr', age=12, school='harvard')
jack.print_id()
jack_son.print_id()
Ahora puedes usar el create
método de clase como un método de fábrica para crear clases padre / hijo con un valor predeterminado para ugly_init
. Tenga en cuenta que debe utilizar parámetros con nombre para que este enfoque funcione.
Si tienes alguna sospecha y disposición de desarrollar nuestro enunciado puedes añadir un informe y con mucho placer lo observaremos.