Contenido del capítulo

Empezamos con los cuatro pilares fundamentales de la programación orientada a objetos. La herencia de clases, es uno de ellos.

En esta completa guía de herencia en Python aprenderás los conceptos base para seguir progresando en la programación orientada a objetos.

Duración estimada de todo el contenido:
Duración del vídeo:
Contiene 6 ejercicios Contiene 4 vídeos.
Tabla de contenidos
Logo

    ¿Qué es la Herencia de Clases?

    Herencia de Clases en Python - Parte 1
    Herencia de Clases en Python - Parte 1

    Gracias a la herencia de clases, podemos crear nuevas clases que posean características y funcionalidades de otras clases, sin repetir código innecesariamente.

    Herencia de clases en Python

    Para que lo entiendas fácil, vamos a ver un ejemplo con una serie de clases que representarían a personajes en un videojuego.

    Si no sabes mucho o nada de videojuegos, no pasa nada, interpreta a estos personajes, como usuarios en una aplicación.

    Herencia se traduce al inglés como inheritance.

    Espacio publicitario

    La clase object

    La clase object es la clase base de todas las clases en Python. Esto significa que todas las clases, ya sean definidas por el usuario o incorporadas en el lenguaje, heredan de la clase object.

    La clase object proporciona una serie de atributos y métodos básicos que son comunes a todas las clases.

    Por así decirlo, podríamos llamar a esta, la clase raíz.

    Ejemplo práctico de herencia

    Empecemos construyendo una sencilla clase llamada Ciudadano, la cual, va a ser el molde general para cualquier ciudadano de un juego de mundo abierto (sandbox):

    class Ciudadano:
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion
    
        def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")
    

    En esta clase, tenemos dos atributos iniciales, nombre y profesion.

    Los objetos creados a partir de ella, son capaces de saludar con su nombre y ocupación, mediante el método saludar().

    Esta clase Ciudadano, va a servir, de forma general, para distintos tipos de personajes. Por decirlo de otra forma, creará ciudadanos genéricos.

    Lo siguiente que quiero, es tener ciudadanos de tipo médico. Entonces, debajo de la primera clase, creo otra clase:

    class Medico:
        def __init__(self, nombre):
            self.nombre = nombre
            self.profesion = "médico"
    
      def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")

    Al objeto de tipo Medico, no necesitamos asignarle una profesión en la instanciación, ya que siempre será médico en ese atributo, por eso lo inicializo así.

    El problema aquí, es que se repite código innecesariamente. Ambas clases son similares.

    Además, si a la clase Ciudadano, le añadimos más atributos como altura, color de pelo, edad, etc., y métodos como saltar, correr, andar, etc., todos estos miembros serían aplicables también en la clase Medico. Así, que tendríamos que repetirlo todo en las dos clases.

    Es aquí donde entra en juego la herencia de clases. Gracias a esta técnica, es posible importar o heredar todo lo que necesitemos de una clase, sin tener que estar añadiendo código repetido.

    Espacio publicitario

    Clase base y clase derivada

    Cuando hablamos de herencia, llamamos clase base a la clase principal. En el ejemplo anterior, la clase base será Ciudadano, ya que habrá otras que heredarán de ella.

    A la clase base, se le denomina de otras formas como clase padre, clase madre, superclase, etc.

    En cambio, la clase Medico será la clase derivada.

    A la clase derivada, se le denomina de otras formas como clase hijo o clase hija, subclase, etc.

    En inglés, clase base se traduce como base class, mientras que clase derivada se traduce como derived class.

    A partir de este punto, me referiré casi todo el tiempo a la clase base como superclase, y a la clase derivada como subclase.

    En la clase Medico, vamos a reutilizar todo lo posible de la clase Ciudadano.

    Aquí tienes el código completo:

    class Ciudadano:
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion
    
        def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")
    
    class Medico(Ciudadano):
        def __init__(self, nombre):
            super().__init__(nombre, "médico")

    Puesto que las clases tienen tanto en común, apenas necesitamos código para la clase Medico.

    En este caso, la clase Ciudadano tiene dos atributos, nombre y profesion. Además, de un método llamado saludar(), que muestra un mensaje con el nombre y la profesión del ciudadano.

    La clase Medico, la hacemos heredar de Ciudadano, especificándola entre los paréntesis:

    class Medico(Ciudadano):

    Con esto tan simple, estamos haciendo que la clase Medico tenga a su disposición, todo el código de la clase Ciudadano.

    Espacio publicitario

    La función predefinida super()

    La función predefinida super() nos permite como objetivo principal que las subclases accedan y reutilicen métodos y atributos de sus superclases.

    El nombre de la función super() se refiere a superclass en inglés.

    Significa superclase en español.

    Si ponemos algo como esto:

    super().__init__()

    Estaremos llamando dentro de una subclase, al método __init__ de la superclase.

    En la clase Medico, utilizamos el contenido del método __init__ de Ciudadano, especificando el valor del atributo profesion, como "médico", que es lo único diferente en la clase:

    def __init__(self, nombre):
        super().__init__(nombre, "médico")

    Es decir, vamos a poder utilizar el método __init__ de la superclase, en la subclase, con un argumento por defecto para el atributo profesion ("médico").

    Por un lado, empezamos con el propio __init__ de la clase Medico. Este solo requiere el atributo nombre. Entonces, es lo que vamos a tener que pasarle en la instanciación.

    def __init__(self, nombre):

    Por ejemplo:

    medico_1 = Medico("Ana")
    print(medico_1.profesion)
    Resultado en la consola
    médico

    Esta salida en la consola demuestra que gracias a la herencia de clases, la clase Medico, aun sin tener el atributo profesion en su código, lo tiene heredado y se le asigna el valor de forma automática en la instanciación.

    Lo que hacemos dentro del __init__ de la clase Medico, es llamar al método __init__ de la superclase, dándole los valores que requiere.

    Por un lado, el valor para el atributo nombre se le pasa en la instanciación del objeto de tipo Medico, y por otro, le proporcionamos un valor "médico" por defecto, para el atributo profesion. Así no hay que especificarlo cada vez; lo tenemos automáticamente.

    super().__init__(nombre, "médico")

    El método saludar(), ya está heredado. Esto quiere decir, que no hay que repetirlo, y que los ciudadanos de tipo Medico, ya tienen la habilidad heredada de saludar().

    ¿Cómo se ha heredado?

    Espacio publicitario

    Al poner la herencia en la declaración de la clase, heredamos todo de la superclase.

    Tan solo hay que utilizar la función predefinida super(), para acceder, y opcionalmente modificar partes concretas de la superclase.

    En el caso de que no necesites cambiar nada del __init__ de la superclase, no hace falta que utilices super(). Esta función es para hacer cambios en lo heredado. Puesto que quiero emplear diferencias en la inicialización de los objetos, lo necesito. En caso contrario, no.

    En el siguiente ejemplo tengo heredado todo de la clase Ciudadano, sin hacer ningún tipo de variación en el __init__. En este caso, tendré que pasar siempre el argumento profesion al instanciar un objeto de tipo Medico:

    class Ciudadano:
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion
    
        def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")
    
    class Medico(Ciudadano):
        pass
    
    medico_1 = Medico("Ana", "médico")
    print(medico_1.profesion)
    Resultado en la consola
    médico

    Volvamos al ejemplo anterior, en el que utilizamos super() para el __init__. Hagamos alguna prueba, para comprobar si podemos crear ciudadanos genéricos y ciudadanos médicos.

    Voy a crear un objeto de cada tipo:

    persona1 = Ciudadano("Julia", "informática")
    persona2 = Medico("Raúl")

    En el caso de persona1, hay que indicar la profesion, aparte del nombre, ya que en esta clase, no hay un valor establecido por defecto para este atributo.

    En el caso de persona2, solo hay que indicar el nombre, puesto que la profesión de médico, viene establecida por defecto en los objetos de tipo Medico.

    Ahora, supongamos que en casos concretos, no quieres ese valor de "médico" por defecto para la clase Medico, más bien, quieres especificar exactamente una especialidad.

    En este caso, puedes utilizar los parámetros igual que en la superclase, dejas profesion sin indicar un valor y se lo tendrás que indicar en la instanciación, al igual que con cada objeto de Ciudadano:

    class Medico(Ciudadano): 
        def __init__(self, nombre, profesion):
            super().__init__(nombre, profesion)
    
    persona2 = Medico("Raúl", "cirujano")

    Puede parecer una tontería entonces utilizar super() en este caso, sin embargo, si no lo utilizas, repetirás código:

    class Ciudadano:
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion
    
        def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")
    
    class Medico(Ciudadano): 
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion

    Practiquemos un poco más. Vamos a añadir un nuevo tipo de ciudadano, Policia. Para ello, crearemos otra clase, que herede de la clase Ciudadano:

    class Policia(Ciudadano): 
        def __init__(self, nombre):
            super().__init__(nombre, "policía")

    Probemos si nos funciona el método saludar(), que no hemos tocado para nada. Simplemente, lo hemos heredado con los dos tipos de ciudadanos.

    class Ciudadano:
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion
    
        def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")
    
    class Medico(Ciudadano):
        def __init__(self, nombre):
            super().__init__(nombre, "médico")
    
    class Policia(Ciudadano): 
        def __init__(self, nombre): 
            super().__init__(nombre, "policía")
    
    persona1 = Ciudadano("Julia", "informática")
    persona2 = Medico("Raúl")
    persona3 = Policia("Raquel")
    
    persona1.saludar()
    persona2.saludar()
    persona3.saludar()
    Resultado en la consola
    Hola, soy Julia. Mi profesión es informática.
    Hola, soy Raúl. Mi profesión es médico.
    Hola, soy Raquel. Mi profesión es policía.

    Espacio publicitario

    Añadir características a las subclases

    Comportamiento y estado diferente en subclases de Python
    Comportamiento y estado diferente en subclases de Python

    En la herencia de clases, aparte de reutilizar código, tenemos otras ventajas. Por ejemplo, podemos crear estados y comportamientos de los objetos con diferencias frente a la superclase ¿Para qué querer varias clases si no añaden nada o casi nada nuevo?

    Para crear estados diferentes, podemos aplicar atributos nuevos a la subclase, atributos que no están en la superclase. Y para el comportamiento, podemos añadir métodos nuevos.

    Aquí tienes un ejemplo:

    class Policia(Ciudadano):
        def __init__(self, nombre):
            super().__init__(nombre, "policía")
    
        def pedir_refuerzos(self):
            print("A todas las unidades, solicito refuerzos. Repito, solicito refuerzos.")
    
    persona3 = Policia("Raquel")
    
    persona3.pedir_refuerzos()
    Resultado en la consola
    A todas las unidades, solicito refuerzos. Repito, solicito refuerzos.

    Los objetos de tipo Policia, ahora tienen un comportamiento diferente al del resto de objetos, ya que son capaces de pedir refuerzos.

    Si intentas utilizar este método con alguna de las otras clases, recibirás un error de atributo inexistente:

    persona2 = Medico("Raúl")
    persona2.pedir_refuerzos()
    Error en la consola
    AttributeError: 'Medico' object has no attribute 'pedir_refuerzos'
    Error de atributo: el objeto de la clase 'Medico' no tiene el atributo 'pedir_refuerzos'

    Espacio publicitario

    Sobrescritura de atributos y métodos

    Sobrescritura de atributos y métodos en Python
    Sobrescritura de atributos y métodos en Python

    Para llevar a cabo lo que se conoce como sobrescritura de métodos y atributos, lo haremos en Python de una forma muy simple. Tan solo hay que crear elementos con la misma identidad, es decir, mismo nombre de método o atributo de una superclase, en una subclase, sobrescribe.

    Quedará más claro con un ejemplo.

    Sigamos con el código de los ejemplos anteriores. Supongamos que queremos tener un método saludo() diferente para la clase Policia. En ese caso, definimos un método que se llame exactamente igual, que lo que hará, es que cuando lo llamemos con un objeto de esta clase Policia, utilice su propio método y deje de heredar el de la superclase.

    En el siguiente ejemplo, tienes el código completo, para poder visualizarlo bien. Fíjate lo sencillo y fácil que es sobrescribir cosas en Python. Esto nos da una flexibilidad enorme a la hora de trabajar con objetos.

    class Ciudadano:
        def __init__(self, nombre, profesion):
            self.nombre = nombre
            self.profesion = profesion
    
        def saludar(self):
            print(f"Hola, soy {self.nombre}. Mi profesión es {self.profesion}.")
    
    class Medico(Ciudadano):
        def __init__(self, nombre):
            super().__init__(nombre, "médico")
    
    class Policia(Ciudadano):
        def __init__(self, nombre):
            super().__init__(nombre, "policía")
    
        def saludar(self):
            print("En la oscuridad, la luz brillará. En la injusticia, la ley prevalecerá. Siempre serviré y protegeré.")
    
    policia1 = Policia("Raquel")
    policia1.saludar()
    
    print("\n") # Salto de línea para separar
    
    medico1 = Medico("Javier")
    medico1.saludar()
    Resultado en la consola
    En la oscuridad, la luz brillará. En la injusticia, la ley prevalecerá. Siempre serviré y protegeré.
    
    Hola, soy Javier. Mi profesión es médico.

    Espacio publicitario

    Ver a qué clase pertenece un objeto

    Cuando estemos trabajando con herencia de clases, y empecemos a tener muchas clases, nos puede ser útil saber a qué clase pertenece un objeto.

    Recuerda, que anteriormente te he mostrado de qué forma puedes ver la clase a la que pertenece un objeto, con objetos propios del lenguaje Python.

    También puedes hacer lo mismo con tus propias clases, no es algo exclusivo para las clases de Python:

    class Usuario:
        pass
    
    persona = Usuario()
    
    print(type(persona))
    Resultado en la consola
    <class '__main__.Usuario'>

    En este caso, nos aparece la clase Usuario, pero también una variable especial llamada __main__. Esta se reemplaza por el nombre del módulo en el que está la clase Usuario. Si aparece __main__, quiere decir que estás ejecutando la función type() desde el mismo archivo en el que está creada la clase.

    En cambio, si haces esto mismo, desde otro módulo, se sustituye el __main__, por el nombre del módulo:

    import test
    print(type(test.persona))
    Resultado en la consola
    <class 'test.Usuario'>

    Si no sabes nada sobre crear módulos (archivos de código Python), con los que hacer esta prueba, haz lo siguiente:

    • Crea un nuevo archivo llamado test.py. Si no lo llamas así, tendrás que modificar el código anterior, por import nombre_archivo, y en la llamada a type() lo mismo: nombre_archivo.nombre_objeto.
    • Guárdalo en la misma carpeta que el archivo con el que estás practicando los ejemplos del libro. Es decir, necesitas dos archivos .py en la misma carpeta de proyecto.
    • En un archivo deja la clase de los ejemplos.
    • En el otro, escribe este último código.

    Si algo no te sale bien, o no lo sabes hacer, no te preocupes. He dedicado una parte entera de este curso, a enseñarte como funciona el trabajo con diferentes archivos de Python (módulos).

    La variable especial __class__

    Mediante la variable especial llamada __class__, es posible acceder directamente a la clase a la que pertenece un objeto:

    class Usuario:
        pass
    
    persona = Usuario()
    
    print(persona.__class__)
    Resultado en la consola
    <class '__main__.Usuario'>

    El resultado es el mismo que con type(), pero en lugar de llamar a una función, estamos accediendo directamente al valor de la variable __class__.

    Espacio publicitario

    Comprobar las herencias que tiene una clase

    Gracias a un método llamado mro(), podemos generar una lista con las clases en el orden en que se buscan los métodos. La lista comienza con la clase actual y luego incluye todas las superclases, de izquierda a derecha.

    Esta definición puede ser un tanto confusa, ya que en realidad no se refiere a la devolución de métodos, sino al orden en que el intérprete consultará las clases, siguiendo las herencias, para finalmente llegar a los métodos que contienen estas.

    Seguro que queda mucho más claro con algunos ejemplos prácticos.

    Aquí no tenemos herencia explícita, pero sí tenemos una implícita, la de la clase object, de la cual heredan todas las clases de Python:

    class A:
        pass
    
    print(A.mro())
    Resultado en la consola
    [<class '__main__.A'>, <class 'object'>]

    Primero nos aparece la propia clase de consulta, y luego, más a la derecha, su superclase, object.

    La clase object, no cumple con la convención de nombres CapWords, por retrocompatibilidad.

    En el siguiente código, estoy haciendo una herencia explícitamente, en la clase B:

    class A():
        pass
    
    class B(A):
        pass
    
    print(B.mro())
    Resultado en la consola
    [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

    El resultado es primero la propia clase, después la superclase A y finalmente, el nivel de herencia superior, la clase object.

    Espacio publicitario

    Expresar herencia de object explícitamente

    Aunque no es obligatorio, en Python es altamente recomendable añadir explícitamente la herencia de la clase object, en las clases que “no hereden” (lo escribo entre comillas, porque todas las clases acaban heredando de esa clase).

    Entonces, para añadir un plus de legibilidad, puedes poner explícitamente la clase object entre los paréntesis de la declaración de las clases.

    Aquí tienes un ejemplo:

    class A(object):
        pass
    
    class B(A):
        pass

    Esto no cambia nada en cuanto a funcionamiento, solo hace tu código más explícito.

    Tipos de herencia en Python

    Tipos de herencias y MRO (orden de resolución de métodos)
    Tipos de herencias y MRO (orden de resolución de métodos)

    En Python tenemos cinco tipos de herencia:

    • Herencia simple o única
    • Herencia múltiple
    • Herencia multinivel
    • Herencia jerárquica
    • Herencia híbrida

    Veamos cada una de ellas con unos ejemplos.

    Espacio publicitario

    Herencia simple o única

    Herencia simple en Python

    La herencia simple o única, es aquella herencia que proviene de una única superclase (no se tiene en cuenta que esté la herencia de la clase object).

    En inglés, herencia simple se dice single inheritance.

    Entonces, en este tipo de herencia encontramos una superclase y una única subclase.

    Aquí tienes un ejemplo con código Python:

    class A:
        pass
    
    class B(A):
        pass

    Herencia múltiple

    Herencia múltiple en Python

    La herencia múltiple ocurre cuando una subclase recibe herencia de múltiples superclases (más de una).

    En inglés, herencia múltiple se dice multiple inheritance.

    Como ejemplo, he escrito tres clases que no heredan de ninguna (aparte de object), y luego, he creado una cuarta clase, que hereda de estas tres.

    Aquí tienes el ejemplo:

    class A(object):
        pass
    
    class B(object):
        pass
    
    class C(object):
        pass
    
    class D(A, B, C):
        pass
    
    print(D.mro())
    Resultado en la consola
    [<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

    Espacio publicitario

    Herencia multinivel

    Herencia multinivel en Python

    La herencia multinivel es aquella que va heredando de otras herencias.

    En inglés, herencia multinivel se dice multilevel inheritance.

    En este ejemplo, la clase D hereda de una clase C, que a su vez hereda de una clase B, y la B, hereda de la clase A.

    Digamos que tenemos varios niveles de herencias, y con cada subclase, más cosas se van heredando “de rebote” (una subclase hereda no solo de su superclase inmediata, sino también de todas las superclases de las herencias anteriores).

    Entonces, como analogía familiar que se suele emplear con las clases, tendríamos la siguiente jerarquía de herencia genética:

    • Clase A: Abuelos
    • Clase B: Padres
    • Clase C: Hijos
    • Clase D: Nietos

    Veamos un ejemplo con código:

    class A(object):
        pass
    
    class B(A):
        pass
    
    class C(B):
        pass
    
    class D(C):
        pass

    Las herencias multinivel de este ejemplo quedan así:

    Clase Nivel Herencias
    A 1 Object (clase base de Python)
    B 2 Object, A
    C 3 Object, A, B
    D 4 Object, A, B, C

    Entre estos niveles, puedes ir creando nuevas clases que vayan heredando de unas y de otras.

    Lo importante, es que cuando definas estructuras de clases con gran complejidad, que lo hagas con mucha organización, detallando todas las herencias de cada clase; algo como lo de la tabla.

    Esto lo indico, porque este tipo de herencia puede ser difícil de mantener si se vuelve muy compleja, cosa que suele ocurrir si hacemos muchos niveles con gran cantidad de código.

    Espacio publicitario

    Herencia jerárquica

    Herencia jerárquica en Python

    La herencia jerárquica es la que ocurre cuando hay más de una subclase proveniente de una superclase.

    En inglés, herencia jerárquica se dice hierarchical inheritance.

    En ese caso, tenemos una clase principal y varias clases secundarias.

    En código, el ejemplo de la imagen se puede representar de esta forma:

    class A(object):
        pass
    
    class B(A):
        pass
    
    class C(A):
        pass
    
    class D(A):
        pass

    Herencia híbrida o mixta

    Herencia híbrida en Python

    La herencia híbrida ocurre cuando se mezclan varios tipos de herencias en una misma estructura de clases.

    Ten especial cuidado al implementar este tipo de herencia, puesto que se puede volver muy complejo y difícil de manejar.

    En inglés, herencia híbrida se dice hybrid inheritance.

    Si lo utilizas, deberás documentar muy bien tu conjunto de clases. Aunque, por supuesto, lo deberías hacer siempre con cualquier tipo de herencia.

    En código, el ejemplo de la imagen se puede representar de esta forma:

    # Clases base
    class A(object):
        pass
    
    class E(object):
        pass
    
    # Clases derivadas
    class B(A):
        pass
    
    class C(B):
        pass
    
    class D(B):
        pass
    
    class F(A,E):
        pass
    
    class G(F):
        pass

    Espacio publicitario




    Espacio publicitario


    Ejercicios de Python para resolver

    En los siguientes ejercicios (16 - 18) vas a crear una herencia de tipo jerárquica.

    16. Crea una clase llamada Vehiculo, que tenga los siguientes atributos:

    • Color
    • Velocidad máxima

    Añade un método __init__ para estos atributos. Darán un estado inicial a los objetos de tipo Vehiculo.

    Creamos la clase con los dos atributos en el método __init__:

    class Vehiculo():
        def __init__(self, color, velocidad_maxima):
            self.color = color
            self.velocidad_maxima = velocidad_maxima

    17. Crea tres clases más. Una será Coche, Auto o Carro, la siguiente será Motocicleta y la última Camion.

    Todas ellas deberán heredar de la clase Vehiculo. Esto hará la estructura jerárquica.

    Los atributos exclusivos de cada clase, son los siguientes:

    • Coche(): Puertas.
    • Motocicleta(): Transmisión, con un valor por defecto de "manual".
    • Camion(): Carga máxima.

    Todos tendrán su propio método __init__, y heredarán de la superclase los atributos comunes para todos.

    En la clase Vehiculo creo un __init__ con los atributos que van a formar parte de todas las subclases.

    Después, en cada subclase, hago herencia con Vehiculo, y con super() implemento en el __init__ de las subclases, los atributos propios de la superclase, y mantengo el propio de cada subclase.

    Herencia en Python
    class Vehiculo():
        def __init__(self, color, velocidad_maxima):
            self.color = color
            self.velocidad_maxima = velocidad_maxima
    
    class Coche(Vehiculo):
        def __init__(self, color, velocidad_maxima, puertas):
            super().__init__(color, velocidad_maxima)
            self.puertas = puertas
    
    class Motocicleta(Vehiculo):
        def __init__(self, color, velocidad_maxima, transmision="Manual"):
            super().__init__(color, velocidad_maxima)
            self.transmision = transmision
    
    class Camion(Vehiculo):
        def __init__(self, color, velocidad_maxima, carga_maxima):
            super().__init__(color, velocidad_maxima)
            self.carga_maxima = carga_maxima

    18. Instancia un objeto de cada tipo, para comprobar que está funcionando todo como es debido.

    Instanciamos cuatro objetos. Uno para cada clase, con los argumentos que le corresponden a cada uno.

    vehiculo_generico = Vehiculo("Negro", 200)
    coche = Coche("Azul", 220, 4)
    moto = Motocicleta("Roja y blanca", 280)
    camion = Camion("Blanco", 160, 5000)

    Después, deberías comprobar con print() que cada atributo de cada objeto es accesible con la clase que le corresponde. Por ejemplo, el objeto camion, no debería tener el atributo transmision, que es algo propio de la clase Motocicleta.

    Aquí tienes un ejemplo:

    print(vehiculo_generico.color)
    print(vehiculo_generico.velocidad_maxima)

    Si no lo has hecho, haz lo mismo con todos los posibles atributos de los otros tres objetos.


    19. ¿Sabes decirme el tipo de herencia que se está dando en el siguiente código?

    class Animal:
        def __init__(self, nombre, edad):
            self.nombre = nombre
            self.edad = edad
    
        def hacer_sonido(self):
            print(f"El animal {self.nombre} ha emitido un sonido.")
    
    class Perro(Animal):
        def __init__(self, nombre, edad, raza):
            super().__init__(nombre, edad)
            self.raza = raza
    
        def hacer_sonido(self):
            print(f"El perro {self.nombre} está ladrando.")
    
    class Husky(Perro):
        def __init__(self, nombre, edad, color, raza="Husky"):
            super().__init__(nombre, edad, raza)
            self.color = color
    
        def hacer_sonido(self):
            print(f"El husky {self.nombre} está aullando.")

    Además, ¿podrías decirme si se está sobrescribiendo algún método?

    El tipo de herencia es multinivel. Tenemos una superclase (Animal), de ella hereda una subclase (Perro), que a la vez, es la superclase de otra subclase (Husky).

    La estructura se representa tal y como puedes ver en la imagen:

    Herencia multinivel en Python

    Al final, la última subclase, utilizando este tipo de herencia, es la que más miembros tiene. Podríamos decir, que es la clase con más posibilidades.

    Tanto en la clase Perro como en la clase Husky, se está sobrescribiendo el método hacer_sonido(). Esto se consigue dándole el mismo nombre, con los mismos parámetros (en este caso, solo self), pero con una salida en consola diferente para cada subclase.

    Con esto, conseguimos con un “mismo método” tener tres comportamientos diferentes según la clase.

    Espacio publicitario

    20. La clase Clase3, ¿qué tipo de herencia tiene?

    class Clase1():
        pass
    
    class Clase2():
        pass
    
    class Clase3(Clase1, Clase2):
        pass

    El tipo de herencia es múltiple, ya que hereda de múltiples clases.

    21. ¿Esta clase está utilizando herencia?

    class Clase1():
        pass

    Sí. Está utilizando herencia. Aunque no lo especifiquemos de forma explícita, las clases heredan de la clase base Object de Python.

    Por lo tanto, equivale a esto:

    class Clase1(Object):
        pass

    Es recomendable ponerlo siempre que “no haya herencia”, puesto que muestra de forma explícita esa herencia que ocurre de fondo.



    Espacio publicitario