Herencia de clases de datos en Python

Jay Shaw 15 febrero 2024
  1. Herencia en Python
  2. Herencia multinivel en Python
  3. Mezcle atributos predeterminados y no predeterminados entre la clase base y la subclase mediante la herencia de clases de datos en Python
  4. Conclusión
Herencia de clases de datos en Python

Las versiones 3.7 y posteriores introdujeron la herencia clase de datos en Python. Este artículo explica ampliamente las herencias de varios niveles y cómo usar la herencia de clases de datos en Python.

Herencia en Python

La herencia de clases de datos en Python se usa para obtener datos en subclases de su clase principal, lo que ayuda a reducir la repetición de códigos y hace que el código sea reutilizable.

Veamos un ejemplo de herencia:

El programa importa el paquete de la biblioteca dataclass para permitir la creación de clases decoradas. La primera clase que se crea aquí es Padre, que tiene dos métodos de miembro: la cadena nombre y el entero edad.

Luego se crea aquí una subclase de Padre. La clase Niño introduce un nuevo método de miembro: escuela.

Se crea un objeto de instancia jack para la clase Parent, que pasa dos argumentos a la clase. Otro objeto de instancia, jack_son, está hecho para la clase Niño.

Como la clase Niño es una subclase de Padre, los miembros de datos se pueden derivar en la clase Niño. Esta es la característica principal de la herencia de clases de datos en Python.

from dataclasses import dataclass


@dataclass
class Parent:
    name: str
    age: int

    def print_name(self):
        print(f"Name is '{self.name}' and age is= {self.age}")


@dataclass
class Child(Parent):
    school: str


jack = Parent("Jack snr", 35)
jack_son = Child("Jack jnr", 12, school="havard")

jack_son.print_name()

Producción :

C:\python38\python.exe "C:/Users/Win 10/PycharmProjects/class inheritance/2.py"
Name is 'Jack jnr' and age is= 12

Process finished with exit code 0

Herencia multinivel en Python

Como hemos visto cómo funciona la herencia de clases de datos en Python, ahora veremos el concepto de herencia multinivel. Es un tipo de herencia en el que una subclase creada a partir de una clase principal se usa como principal para la clase secundaria subsiguiente.

El siguiente ejemplo demuestra la herencia de varios niveles de forma sencilla:

Clase principal

Se crea una clase Padre con un constructor __init__ y un método miembro: print_method. El constructor imprime la declaración "Inicializado en padre" para que se muestre cuando se invoca la clase desde su clase secundaria.

La función print_method tiene un parámetro b, que se imprime cuando se llama a este método.

Clase infantil

La clase Child se deriva de Parent e imprime una declaración dentro de su constructor. El super().__init__ se refiere a la clase base con la clase secundaria.

Usamos super() para que cualquier herencia múltiple cooperativa potencial utilizada por las clases secundarias llame a la siguiente función de clase principal apropiada en el Orden de resolución de métodos (MRO).

El método miembro print_method está sobrecargado y una declaración de impresión imprime el valor de b. Aquí también, super() se refiere al método miembro de su clase padre.

Clase de nieto

En este punto, el programa simplemente repite (repetir códigos) la estructura para crear otra clase heredada de la clase Niño. Dentro de print_method, el valor de b se incrementa usando super().

Función principal

Por último, se crea la función main, que crea un objeto ob y se convierte en una instancia de GrandChild(). Por último, el objeto ob invoca el print_method.

Así es como se apilan las clases de varios niveles cuando se usa la herencia de clases de datos en Python.

class Parent:
    def __init__(self):
        print("Initialized in Parent")

    def print_method(self, b):
        print("Printing from class Parent:", b)


class Child(Parent):
    def __init__(self):
        print("Initialized in Child")
        super().__init__()

    def print_method(self, b):
        print("Printing from class Child:", b)
        super().print_method(b + 1)


class GrandChild(Child):
    def __init__(self):
        print("Initialized in Grand Child")
        super().__init__()

    def print_method(self, b):
        print("Printing from class Grand Child:", b)
        super().print_method(b + 1)


if __name__ == "__main__":
    ob = GrandChild()
    ob.print_method(10)

Producción :

C:\python38\python.exe "C:/Users/Win 10/PycharmProjects/class inheritance/3.py"
Initialized in Grand Child
Initialized in Child
Initialized in Parent
Printing from class Grand Child: 10
Printing from class Child: 11
Printing from class Parent: 12

Process finished with exit code 0

Entendamos lo que hace el código aquí:

La función main pasa el valor 10 a la función print_method de la clase Grand Child. Según el MRO (Orden de resolución de métodos), el programa primero ejecuta la clase Grand Child, imprime la declaración __init__ y luego pasa a su padre, la clase Child.

La clase Child y la clase Parent imprimen sus declaraciones __init__ según MRO, y luego el compilador vuelve al print_method de la clase GrandChild. Este método imprime 10 (el valor de b) y luego usa super() para incrementar el valor de b en su superclase, la Clase Niño.

Luego, el compilador pasa al método de impresión de la clase Niño e imprime 11. Luego, en el último nivel de MRO está la clase Padre que imprime 12.

El programa sale porque no existe ninguna superclase sobre la clase Padre.

Como hemos entendido cómo funciona la herencia multinivel en la herencia de clases de datos en Python, la siguiente sección cubrirá el concepto de heredar atributos de una clase principal y cómo modificarlos.

Mezcle atributos predeterminados y no predeterminados entre la clase base y la subclase mediante la herencia de clases de datos en Python

Hemos visto cómo usar una clase secundaria para acceder a los miembros de datos de su clase principal en la herencia de clases de datos en Python y cómo funciona la herencia de varios niveles. Ahora, surge una pregunta si una clase secundaria puede acceder a los datos de los miembros de su superclase, ¿puede hacerle cambios?

La respuesta es sí, pero debe evitar TypeErrors. Por ejemplo, en el siguiente programa, hay dos clases, una clase Padre y una subclase Niño.

La clase Parent tiene tres miembros de datos: name, age y una variable bool ugly que se establece como False de forma predeterminada. Los métodos de tres miembros imprimen el nombre, la edad y la identificación.

Ahora, dentro de la clase decorada Niño derivada de Padre, se introduce un nuevo miembro de datos: escuela. Con él, la clase cambia el atributo de la variable feo de False a True.

Se crean dos objetos, jack para Padre y jack_son para Niño. Estos objetos pasan argumentos a sus clases, y ambos objetos invocan el método print_id e imprimen los detalles.

Un problema importante con el uso de este método para cambiar los valores predeterminados de la clase base es que provoca un TypeError.

from dataclasses import dataclass


@dataclass
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"ID: Name - {self.name}, age = {self.age}")


@dataclass
class Child(Parent):
    school: str
    ugly: bool = True


jack = Parent("jack snr", 32, ugly=True)
jack_son = Child("Mathew", 14, school="cambridge", ugly=True)

jack.print_id()
jack_son.print_id()

Producción :

    raise TypeError(f'non-default argument {f.name!r} '
TypeError: non-default argument 'school' follows default argument

Una razón detrás de este error es que en la herencia de clases de datos en Python, los atributos no se pueden usar con los valores predeterminados en una clase base y luego se pueden usar sin un valor predeterminado (atributos posicionales) en una subclase debido a la forma en que las clases de datos mezclan los atributos.

Esto se debe a que los atributos se fusionan desde el principio en la parte inferior del MRO y crean una lista ordenada de atributos en el orden en que se ven por primera vez, y las anulaciones permanecen en sus posiciones originales.

Con feo por defecto, el Padre comienza con nombre, edad y feo, y luego el Niño añade escuela al final de esa lista (con feo ya en el lista).

Esto da como resultado tener nombre, edad, feo y escuela en la lista, y dado que escuela no tiene un valor predeterminado, la función __init__ muestra el resultado como un parámetro incorrecto.

Cuando el decorador @dataclass crea una nueva clase de datos, busca en todas las clases base de la clase en MRO inverso (comenzando en el objeto) y agrega los campos de cada clase base a un mapeo ordenado de campos para cada clase de datos. encuentra

Luego agrega sus campos a la asignación ordenada después de que se hayan agregado todos los campos de la clase base. Todos los métodos creados utilizarán esta asignación ordenada calculada combinada de campos.

Debido a la disposición de los campos, las clases derivadas reemplazan a las clases base.

Si un campo sin valor predeterminado sigue a uno con un valor predeterminado, se generará un TypeError. Esto es cierto ya sea que suceda en una sola clase o debido a la herencia de clases.

La primera alternativa para solucionar este problema es forzar los campos con valores predeterminados a una posición posterior en el orden MRO utilizando diferentes clases base. Evite establecer campos directamente en las clases que se utilizarán como clases base, como Padre, a toda costa.

Este programa tiene clases base con campos, y los campos sin valores predeterminados están separados. Las clases públicas se derivan de las clases base-con y base-sin.

Las subclases de la clase pública ponen la clase base al frente.

from dataclasses import dataclass


@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"ID: Name - {self.name}, age = {self.age}")


@dataclass
class Child(_ChildDefaultsBase, Parent, _ChildBase):
    pass


Amit = Parent("Amit snr", 32, ugly=True)

Amit_son = Child("Amit jnr", 12, school="iit", ugly=True)

Amit.print_id()
Amit_son.print_id()

Producción :

C:\python38\python.exe "C:/main.py"
The Name is Amit snr and Amit snr is 32 year old
The Name is Amit jnr and Amit jnr is 12 year old

Process finished with exit code 0

El MRO creado aquí prioriza los campos sin valores predeterminados sobre los campos con valores predeterminados dividiendo los campos en clases base separadas con campos “sin valores predeterminados” y “con valores predeterminados” y eligiendo cuidadosamente el orden de herencia. El MRO del Niño es:

<class 'object'>
        ||
<class '__main__._ParentBase'>,
        ||
<class '__main__._ChildBase'>
        ||
<class '__main__._ParentDefaultsBase'>,
        ||
<class '__main__.Parent'>,
        ||
<class '__main__._ChildDefaultsBase'>,
        ||
<class '__main__._Child'>

Aunque Parent no crea ningún campo nuevo, hereda los campos de ParentDefaultsBase y no debería ocupar el último lugar en el orden de la lista de campos. Por lo tanto, la ChildDefaultsBase se mantiene finalmente para cumplir con el tipo de pedido correcto.

Las reglas de clase de datos también se cumplen ya que las clases ParentBase y ChildBase, que tienen campos sin valores predeterminados, vienen antes que ParentDefaultsBase y ChildDefaultsBase, que tienen campos con valores predeterminados.

Como resultado, Child sigue siendo una subclase de Parent, mientras que las clases Parent y Child tienen un orden de campo correcto:

# __ Program Above __

print(signature(Parent))
print(signature(Child))

Producción:

Función de firma

Conclusión

Este artículo explica en detalle la herencia de clases de datos en Python. Conceptos como clase de datos, clases secundarias, herencia multinivel y atributos de mezcla de la clase base a la subclase se explican detalladamente.

Artículo relacionado - Python Class

Artículo relacionado - Python Dataclass