Saltar al contenido

Herencia de clases en clases de datos de Python 3.7

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 en object) 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 de x es int, como se especifica en la clase C.

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *