Curso de introducción a la programación con Python

Autor: Luis Fernando Apáez Álvarez

Clase 8: Herencia simple

Uno de los conceptos más importantes referentes a la programación orientada a objetos es el de la Herencia.

Recordemos de la sesión pasada que en la clase Abuelo definimos el constructor como

class Abuelo():

    # Constructor

    def __init__(self, nombre):

        # Atributos
        self.nombre = nombre
        self.apellido = 'Apáez'
        self.color_ojos = 'negro'
        self.color_cabello = 'marrón'
        self.tez = 'morena'

        self.hablando = False
        self.corriendo = False

de donde, en la clase Hijo (subclase de la superclase Abuelo) también creamos un método inicial:

class Hijo(Abuelo):

    # Constructor:
    # Agregamos el parámetro (o los parámetros) del constructor de la superclase

    def __init__(self, nombre, num_celular):

        # hacemos referencia de los parámetros de la superclase 
        super().__init__(nombre)

        # Atributos
        self.num_celular = num_celular
        self.idiomas = ['Inglés', 'Francés', 'Italianos']
        self.estudiar = False
        self.pedalear = False

en el cual "pasamos" los parámetros del constructor de la clase Abuelo e hicimos la referencia de ello utilizando super().__init__(nombre).

En otras palabras, incorporamos el método __init__ desde la superclase Abuelo a la subclase Hijo, agregando algunas características propias dentro de este método en la clase Hijo

Captura3.PNG

Sobreescritura de métodos

El comportamiento anterior respecto al método __init__ se conoce como sobreescritura de métodos, la cual consiste básicamente en definir un método en la superclase y volverlo a definir en una subclase; además de efectuar esta re-definición podemos agregar cosas distintas al método sobreescrito.

De tal manera, sobreescribimos el método __init__ de la superclase Abuelo en la subclase Hijo, agregando algunos atributos adicionales que no figuran en el método __init__ de la superclase.

Abordemos otro ejemplo sobre la sobreesctritura de métodos, para ello recordemos el código de la superclase Abuelo agregando un método para mostrar objetos (__str__)

y también el código de la subclase Hijo (cuya superclase es la clase Abuelo)

podemos después crear un objeto de la clase Hijo y observar como se hereda el método __str__ de la superclase Abuelo a la subclase Hijo

donde el método __str__ nos devuelve una cadena de texto con un saludo y el nombre del objeto. Sin embargo, en la subclase Hijo tenemos un atributo importante que la superclase Abuelo no tiene, nos referimos al atributo self.num_celular. De tal manera, es de nuestro interés que el método __str__ también muestre el número teléfonico referente a dicho atributo, por lo que debemos efectuar una sobreescitura del método __str__ en la subclase Hijo para que se muestre lo que deseamos. Para ello escribimos

donde al declarar el método __str__ dentro de la subclase Hijo estamos realizando una sobreescritura de métodos. Luego, en la primer parte del f-string dentro del método __str__ hacemos la referencia correspondiente al mensaje que habíamos escrito en el método __str__ de la superclase Abuelo, consiguiendo lo anterior al escribir super().__str__(), de manera análoga a cuando lo hicimos con el método __init__. De tal manera en el método __str__ de la subclase Hijo:

Veamos pues que

Otro ejemplo

Consideremos la clase Punto2 que hace alusión a puntos sobre un espacio de dos dimensiones ($\mathbb{R}^{2}$), además de los habituales métodos __init__ y __str__ escribiremos el método norma para calcular la norma de dicho punto.

Observación: Considerando un punto sobre el plano cartesiano, $P=(x,y)\in\mathbb{R}^{2}$, la norma de $P$ se define como el número $\sqrt{x^{2}+y^{2}}$.

Para ejemplificar la sobreescritura de métodos más adelante, haremos la construcción del método para calcular la norma de un punto en dos partes como veremos a continuación

Y creamos otra clase denominada Punto3 que haga alusión a los puntos en un espacio de tres dimensiones y que sea totalmente análoga a la clase Punto2, es decir que posea los mismos atributos y métodos adaptados al espacio de tres dimensiones. Tenemos la opción de crear dicha clase escribiendo

class Punto3():

    # Constructor
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    # __str__
    def __str__(self):
        return f'{self.x, self.y, self.z}'

    # Método norma:
    def auxiliar(self):
        return self.x ** 2 + self.y ** 2 + self.z ** 2

    def norma3(self):
        return np.sqrt(self.auxiliar())

pero también podemos efectuar sobreescritura de métodos en los métodos __init__, __str__ y auxiliar de la clase Punto2 como sigue


Así como hemos construido la clase Punto3 como subclase de Punto2, podríamos construir la clase Punto4 como subclase de Punto3, de tal manera

La idea anterior puede ser más clara si recordamos le herencia entre personas

Captura.PNG

considerando cada rango jerárquico como una clase, tenemos entonces que la clase Abuelo es superclase de las clases Hijo, Nieto1 y Nieto2, donde éstas últimas son subclases de la superclase Hijo. Es por ello que la clase Punto4 seguiría la misma idea de las clases Nieto1 y Nieto2 respecto a la imagen anterior. Esto es

Captura6.PNG

Escribamos pues el código de la clase Punto4 en la cual volveremos a efectuar sobreescritura de métodos en los métodos __init__, __str__ y norma

lo cual ejemplifica perfectamente la sobreescritura del método __str__, donde el primer paréntesis $(3,4)$ hace alusión al método __str__ de la clase Punto2, el segundo $((3,4),5)$ a la clase Punto3 y el último $(((3,4),5),6)$ a la propia clase Punto4.

Hasta el momento, todo lo que hemos trabajado sobre herencia hace referencia al concepto de herencia simple, la cual se caracteriza cuando las subclases tienen sólo una superclase en cuestión. Por ejemplo, sobre la clase Abuelo y la subclase Hijo tenemos herencia simple; sobre la clase Punto2 y la subclase Punto3 tenemos herencia simple, asimismo sobre la clase Punto3 y Punto4 tenemos herencia simple.

Observación: Debemos ser cautelosos cuando la herencia tenga más de un nivel jerárquico y ser claros en que la herencia se involucra en todos los niveles de forma conjunta. Es decir, no podemos pensar la herencia de la clase Punto2 sobre la subclase Punto4 de manera aislada, pues en medio interviene la clase Punto3. Lo cual es claro, pues por ejemplo (en la herencia entre personas) no podemos pensar a los nietos como descendencia directa de los abuelos, pues en medio interviene de manera contundente los padres en cuestión (hijos de los abuelos).

Notemos, en realidad, que en la herencia entre persona hay dos papeles decisivos en vez de uno

Captura7.PNG

o trasladado a lenguaje de programación tendríamos algo del estilo

Captura5.PNG

donde la Subclase2 y Subclase3 tienen dos superclases en cuestión y por ello podemos hablar en este caso de herencia múltiple. Además, la Subclase1 sólo tiene una superclase en cuestión por lo que en este caso hablamos sobre herencia simple. En las subsecuentes sesiones abordaremos más a detalle el tema de la herencia múltiple.

Socialmedia.PNG