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.