Herencia con Python

La herencia en Python consiste en definir una clase secundaria o derivada que hereda todas las propiedades y métodos de otra clase principal o base, es decir, en crear una clase a partir de otra que ya existe.

  • Clase principal o base.
  • Clase secundaria o derivada.

Herencia en POO Python

Cuando hablamos de herencia en Python también nos podemos referir a nuestras clases como clase madre para la clase principal y clase hija para las secundarias. Nosotros vamos a utilizar clase base para la principal y clase secundaria para hacer referencia a la clase que hereda de la clase base.

Comenzaremos viendo cómo se escribe una clase de Python que hereda de otra. La única diferencia al definirlas es que la clase secundaria tiene entre paréntesis el nombre de la clase base.

Documentación oficial de Python sobre clases.

class Clase_base:
    pass


class Clase_secundaria(Clase_base):
    pass

Ahora veamos un ejemplo con clases de verdad.

Primero crearemos la clase Consolas, que será la clase base. Todas las consolas que vamos a crear tendrán en común los atributos marca y lanzamiento. Por otro lado, los tres métodos que hemos definido (encender, apagar e imprimir información) también podrán ser utilizados por las clases secundarias o hijas.

class Consolas:
    def __init__(self, marca, lanzamiento):
        self.marca = marca
        self.lanzamiento = lanzamiento

    def encender(self):
        return f'La consola {self.marca} está encendida!'

    def apagar(self):
        return f'La consola {self.marca} está apagada!'

    def imprime_info(self):
        return f'La consola {self.marca} se lanzó en {self.lanzamiento}'

Ahora definiremos la clase secundaria Megadrive, que hereda de la clase base Consolas y tiene un método propio cara cargar juegos de 16 bits.

class Megadrive(Consolas):
    def cargo_juego_16bits(self):
        return f'Juego de 16 bits cargado en la consola {self.marca}'

Crearemos un objeto llamado mi_megadrive instanciando la clase secundaria Megadrive que hereda de la clase base Consolas.

Haremos uso del método encender definido en la clase base y del método cargo_juego_16bits en la clase secundaria.

mi_megadrive = Megadrive('Sega', 1988)
print(mi_megadrive.encender())
print(mi_megadrive.cargo_juego_16bits())

# La consola Sega está encendida!
# Juego de 16 bits cargado en la consola Sega

Vamos a ver todo el ejemplo anterior en un solo bloque de código para que se entienda mejor.

class Consolas:
    def __init__(self, marca, lanzamiento):
        self.marca = marca
        self.lanzamiento = lanzamiento
    def encender(self):
        return f'La consola {self.marca} está encendida!'
    def apagar(self):
        return f'La consola {self.marca} está apagada!'
    def imprime_info(self):
        return f'La consola {self.marca} se lanzó en {self.lanzamiento}'

class Megadrive(Consolas):
    def cargo_juego_16bits(self):
        return f'Juego de 16 bits cargado en la consola {self.marca}'

mi_megadrive = Megadrive('Sega', 1988)
print(mi_megadrive.encender())
print(mi_megadrive.cargo_juego_16bits())

# La consola Sega está encendida!
# Juego de 16 bits cargado en la consola Sega

Ahora vamos a crear otra clase secundaria que herede también de Consolas.

class Gameboy(Consolas):
    def coloco_pilas(self):
        return f'Pilas colocadas en la consola de la marca {self.marca}'

Ahora lo primero que haremos será instanciar la clase y así crear un objeto llamado mi_gameboy. Después, llamaremos al método de la clase secundaria coloco_pilas para verificar si las pilas funcionan, llamando a los métodos de la clase principal encender y apagar.

mi_gameboy = Gameboy('Nes', 1989)
print(mi_gameboy.coloco_pilas())
print(mi_gameboy.encender())
print(mi_gameboy.apagar())

# Pilas colocadas en la consola de la marca Nes
# La consola Nes está encendida!
# La consola Nes está apagada!

Con este ejemplo que acabamos de ver, podemos observar que los bloques de código para encender y apagar nuestras consolas se están reutilizando desde la clase principal.

__init__() con herencia

Si observamos nuestra clase principal podemos ver que contiene el método __init__(). Si nos fijamos en nuestras clases secundarias vemos que no existe este método en ninguna de las dos.

Si nosotros agregamos el método __init__() en nuestra función secundaria, ya no heredaremos este método de la clase principal.

Lo vamos a ver con un ejemplo modificando las clases anteriores. Eliminaremos algunos métodos de la clase principal y agregaremos __init__() a la clase secundaria.

class Consolas:
    def __init__(self, marca, lanzamiento):
        self.marca = marca
        self.lanzamiento = lanzamiento

    def encender(self):
        return f'La consola está encendida!'

class Megadrive(Consolas):
    def __init__(self, modelo):
        self.modelo = modelo
    
    def cargo_juego_16bits(self):
        return f'Juego de 16 bits cargado en {self.modelo}'

Al instanciar la clase Megadrive tendremos que indicarle solo el modelo, ya que agregar un __init__() en la clase secundaria, sustituye el __init_() heredado de la clase base.

A los métodos de la clase principal podemos acceder como antes de agregar __init__() en la clase secundaria.

mi_megadrive = Megadrive('Mastersystem')
print(mi_megadrive.encender())
print(mi_megadrive.cargo_juego_16bits())

# La consola está encendida!
# Juego de 16 bits cargado en Mastersystem

Mantener la herencia __init__()

Existe la posibilidad de mantener la herencia de __init__() de la clase principal aun teniendo definido el método __init__() en la clase secundaria.

¿Cómo podemos hacer esto?

Realizando una llamada al método __init__() de la clase base desde nuestra clase secundaria o hija.

Consolas.__init__(self, marca, lanzamiento)

Debemos tener claro que al instanciar nuestra clase y crear el objeto mi_megadrive debemos indicarle tres argumentos: modelo, marca y lanzamiento.

Veamos el bloque de código completo para entenderlo mejor.

class Consolas:
    def __init__(self, marca, lanzamiento):
        self.marca = marca
        self.lanzamiento = lanzamiento

    def encender(self):
        return f'La consola {self.marca} está encendida!'


class Megadrive(Consolas):
    def __init__(self, modelo, marca, lanzamiento):
        Consolas.__init__(self, marca, lanzamiento)
        self.modelo = modelo

    def muestro_info(self):
        return f'La consola {self.modelo} de la marca {self.marca} se lanzó en {self.lanzamiento}'

mi_megadrive = Megadrive('Megadrive', 'Sega', 1988)
print(mi_megadrive.muestro_info())

# La consola Megadrive de la marca Sega se lanzó en 1988

Funciones integradas isinstance e issubclass

Cuando estamos trabajando en programas con muchas líneas de código, es muy útil utilizar estas dos funciones integradas:

  • isinstance (objeto, clase) → Nos permite saber si un objeto pertenece a determinada clase devolviéndonos True o False.
  • issubclass (subclase, clase) → Nos permite saber si una subclase pertenece a una clase devolviéndonos True o False.

Veamos estas dos funciones a partir de algunos ejemplos.

Para empezar crearemos dos clases y un objeto instanciando una de esas clases. Mediante la función isinstance() vamos a ver si el objeto creado es el resultado de instanciar una clase u otra.

class Consolas:
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo

class Juegos:
    def __init__(self, nombre, tecnologia):
        self.nombre = nombre
        self.tecnologia = tecnologia

mi_megadrive = Consolas('Nintendo', 'Gameboy')
print(isinstance(mi_megadrive, Consolas))
# True

print(isinstance(mi_megadrive, Juegos))
# False

Partiendo del ejemplo anterior, crearemos una nueva clase que herede de Consolas; la vamos a llamar Portatiles. Preguntaremos mediante la función issubclass() si nuestra nueva clase pertenece a una clase principal o no.

class Consolas:
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo

class Portatiles(Consolas):
    def encender(self):
        return f'La consola {self.modelo} está encendida!'

class Juegos:
    def __init__(self, nombre, tecnologia):
        self.nombre = nombre
        self.tecnologia = tecnologia

mi_megadrive = Portatiles('Nintendo', 'Gameboy')
print(issubclass(Portatiles, Consolas))
# True

print(issubclass(Portatiles, Juegos))
# False

Herencia múltiple con Python

En Python se conoce como herencia múltiple a la clase secundaria que hereda de más de una clase principal o base.

Vamos a verlo con un ejemplo.

Nuestra clase Gamegear heredará de las clases Consolas y Portatiles, lo que le permite hacer uso de todos sus métodos y propiedades.

class Consolas:
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo
    
    def encender(self):
        return f'La consola {self.modelo} está encendida!'

class Portatiles():
    pilas = True
    tactil = False
    
    def recargar_pilas(self):
        return f'Pilas recargadas!'

class Gamegear(Consolas, Portatiles):
    def mostrar_info(self):
        return f'''
La consola {self.modelo} de la narca {self.marca}
Pilas: {self.pilas}
Táctil: {self.tactil}
        '''

mi_gamegear = Gamegear('Sega', 'Gamegear')

print(mi_gamegear.encender())
# La consola Gamegear está encendida!

print(mi_gamegear.recargar_pilas())
# Pilas recargadas!

print(mi_gamegear.mostrar_info())
# La consola Gamegear de la narca Sega
# Pilas: True
# Táctil: False

Herencia multinivel con Python

Llamamos herencia multinivel a la herencia recibida de una clase que previamente heredó de una clase principal. Vamos a verlo modificando el ejemplo anterior, que será mucho más sencillo de entender.

class Consolas:
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo
    
    def encender(self):
        return f'La consola {self.modelo} está encendida!'

class Portatiles(Consolas):
    pilas = True
    tactil = False
    
    def recargar_pilas(self):
        return f'Pilas recargadas!'

class Gamegear(Portatiles):
    def mostrar_info(self):
        return f'''
La consola {self.modelo} de la narca {self.marca}
Pilas: {self.pilas}
Táctil: {self.tactil}
        '''

mi_gamegear = Gamegear('Sega', 'Gamegear')

print(mi_gamegear.encender())
# La consola Gamegear está encendida!

print(mi_gamegear.recargar_pilas())
# Pilas recargadas!

print(mi_gamegear.mostrar_info())
# La consola Gamegear de la narca Sega
# Pilas: True
# Táctil: False

El resultado es el mismo, pero, si nos fijamos bien, estamos haciendo uso de la herencia multinivel de Python.

La clase Portatiles hereda de Consolas, y la clase Gamegear hereda de Portatiles. Es decir, nuestra clase Gamegear hereda de una clase secundaria, ya que Portatiles hereda de la clase base Consolas.

Dejar un comentario

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