Clases y objetos en Python V
Herencia simple y múltiple
La herencia múltiple consiste, como su nombre lo indica, en la instanciación de clases provenientes de diferentes superclases, es decir, un objeto que hereda propiedades y métodos de diferentes clases. Supongamos que en ejemplo de los vehículos deseamos crear una super clase para vehículos eléctricos y a su vez deseamos crear una subclase para bicicletas eléctricas que heredarán las propiedades y métodos de la clase vehículos eléctricos.
Hasta aquí no hay nada nuevo que no se haya visto, sin embargo las bicicletas eléctricas poseen comportamientos y propiedades que ya están el la superclase vehiculos(), tales como los métodos acelerar() y frenar(), además de las que podamos definir en la clase vehículos_electricos(), como por ejemplo el método carga_electrica() o la propiedad <autonomia> que indica cuantos kilometros puede rodar con una carga entera de la batería.
En este caso se pueden instanciar desde las 2 superclases que poseen las propiedades y métodos requeridos usando la herencia múltiple para poder instanciar clases que contengan propiedades y métodos de diferentes superclases. Esto se logra haciendo la instanciación como la hemos venido haciendo hasta el momento, pero separando con comas las superclases de las cuales se van a heredar las propiedades y métodos de la clase hija. Hay que tomar en cuenta que Python siempre le va a dar prioridad a las superclases de izquierda a derecha para heredar el método constructor, es decir que la primera superclase que asignemos va a ser de la que se herede el método constructor. Veamos el ejemplo en código:
import os
os.system('cls')
class v_electricos():
def __init__(self) -> None:
self.autonomia = 100
def cargar_energia(self):
self.carga_electrica=True
class vehiculos():
def __init__(self,marca,modelo) -> None:
self.marca=marca
self.modelo=modelo
self.rodando=False
self.acelerando=False
self.frenando=False
def encender(self):
self.rodando=True
def acelerar(self):
self.acelerando=True
def frenar(self):
self.frenando=True
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando)
class moto(vehiculos):
inclinacion='Neutral'
def inclinar(self,direccion):
self.inclinacion=direccion
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nInclinación: ',self.inclinacion)
class camioneta(vehiculos):
cargado=False
def carga(self,cargar):
self.cargado=cargar
if self.cargado:
return 'Camioneta cargada'
else:
return 'Camioneta vacía'
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nCargado: ',self.cargado, '\nEstado de la carga: ',\
self.carga(self.cargado))
class bicicleta_electrica(v_electricos,vehiculos):
pass
bicicleta_electrica01 = bicicleta_electrica('BMW','Cruise E-bike')
bicicleta_electrica01.estado()
Resultado:
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\12.1 Herencia.py", line 64, in <module>
bicicleta_electrica01 = bicicleta_electrica('BMW','Cruise E-bike')
TypeError: v_electricos.__init__() takes 1 positional argument but 3 were given
Como podemos observar se ha producido un error, ya que Python le asigna el método constructor de la superclase v_electricos() a la clase bicicleta_electrica01() ya que esta de primera en la creación de la clase hija, pero esta clase no recibe marca y modelo como argumentos en su constructor, con lo cual al intentar instanciar de dichas superclases en ese orden pues Python no encientra los parámetros donde pasar dichos argumentos. Esto se debe a que Python prioriza la herencia de propiedades y metodos de izquierda a derecha, con lo cual el constructor que heredará será el de la clase v_electricos, el cual no admite argumentos adicionales. La solución para acceder a los parámetros marca y modelo del constructor de la clase vehiculos() es colocarlo de primero en la herencia multiple al crear la clase bicicleta_electrica(). Comprobemos con código lo dicho anteriormente:
import os
os.system('cls')
class v_electricos():
def __init__(self) -> None:
self.autonomia = 100
def cargar_energia(self):
self.carga_electrica=True
class vehiculos():
def __init__(self,marca,modelo) -> None:
self.marca=marca
self.modelo=modelo
self.rodando=False
self.acelerando=False
self.frenando=False
def encender(self):
self.rodando=True
def acelerar(self):
self.acelerando=True
def frenar(self):
self.frenando=True
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando)
class moto(vehiculos):
inclinacion='Neutral'
def inclinar(self,direccion):
self.inclinacion=direccion
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nInclinación: ',self.inclinacion)
class camioneta(vehiculos):
cargado=False
def carga(self,cargar):
self.cargado=cargar
if self.cargado:
return 'Camioneta cargada'
else:
return 'Camioneta vacía'
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nCargado: ',self.cargado, '\nEstado de la carga: ',\
self.carga(self.cargado))
class bicicleta_electrica(vehiculos,v_electricos):
pass
bicicleta_electrica01 = bicicleta_electrica('BMW','Cruise E-bike')
bicicleta_electrica01.estado()
Resultado:
********** DATOS DEL VEHICULO **********
Marca: BMW
Modelo: Cruise E-bike
Acelerando: False
Frenando: False
Rodando: False
Como podemos comprobar, además que ahora el intérprete de Python ya no arroja error, se muestra la información de marca y modelo como le fué enviado en el parámetro del constructor de la clase vehículo en la instanciación con herencia múltiple. Todo esto a pesar que la clase bicicleta_electrica() no posea aun ninguna propiedad o método.
Ahora bien, el resto de métodos de las clases de donde se van a heredar propiedades y métodos en una sola sub clase y en donde no coinciden los nombres entre unos y otros, se heredarán todos de ambas superclases ya que no habrán conflictos de nombres entre ellos. De aquí la importancia de definir bien las propiedades y métodos de cada clase para evitar redundancias o carencias al momento de instanciar clases o hacer herencia múltiple. Por ejemplo repetir los métodos encender(), acelerar() y frenar() en la clase v_electrico() sería una práctica poco elegante. Adicionalmente hay que mencionar que se puede hacer herencia múltiple de mas de dos superclases.
¿Es la única solución cambiar el orden de las superclases en la herencia multiple?, pues no. Aunque conviene no complicar el código innecesariamente y por regla general es recomendable buscar siempre la solución más simple y práctica. Sin embargo, en el ejemplo del uso de clases para representar los vehículos y la clase "hermana" para vehiculos electricos, cuyas propiedades y métodos son heredados de manera múltiple por la subclase bicicleta_electrica(), es mas recomendable hacer la clase v_electricos() una subclase de vehiculos() y así la instanciación de dicha subclase heredaría las propiedades y métodos de la clase padre v_electricos() y de la clase "abuela" vehiculos(). Esto sin embargo traería otro problema como veremos en el codigo a continuación, mas allá que se hace necesario cambiar el orden de la declaración CLASS de ambas clases vehiculos() y v_electricos() ya que ahora v_electricos() va a ser hija de vehiculos() y debe ser declarada después que su padre para evitar un error. Veamos como quedaría el código con esta modificación:
import os
os.system('cls')
class vehiculos():
def __init__(self,marca,modelo) -> None:
self.marca=marca
self.modelo=modelo
self.rodando=False
self.acelerando=False
self.frenando=False
def encender(self):
self.rodando=True
def acelerar(self):
self.acelerando=True
def frenar(self):
self.frenando=True
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando)
class v_electricos(vehiculos):
def __init__(self) -> None:
self.autonomia = 100
def cargar_energia(self):
self.carga_electrica=True
class moto(vehiculos):
inclinacion='Neutral'
def inclinar(self,direccion):
self.inclinacion=direccion
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nInclinación: ',self.inclinacion)
class camioneta(vehiculos):
cargado=False
def carga(self,cargar):
self.cargado=cargar
if self.cargado:
return 'Camioneta cargada'
else:
return 'Camioneta vacía'
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nCargado: ',self.cargado, '\nEstado de la carga: ',\
self.carga(self.cargado))
class bicicleta_electrica(v_electricos):
pass
bicicleta_electrica01 = bicicleta_electrica()
bicicleta_electrica01.estado()
Resultado:
********** DATOS DEL VEHICULO **********
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\12.1 Herencia.py", line 68, in <module>
bicicleta_electrica01.estado()
File "e:\Documentos\Desarrollos\Python\Curso-Python\12.1 Herencia.py", line 27, in estado
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
^^^^^^^^^^
AttributeError: 'bicicleta_electrica' object has no attribute 'marca'
Como era de esperarse este cambio arrojó un error, y es que, a pesar que ahora la clase v_electricos() es una clase hija de la superclase vehiculos() y se hace toda la herencia de manera correcta, Python no puede encontrar el atributo marca en la clase instanciada en el objeto bicicleta_electrica, ya que nunca se le enviaron los argumentos marca y modelo al constructor de la clase padre original que es la única que tiene esos parámetros. ¿Cómo crear un sistema de herencia de clases en donde se puedan pasar argumentos desde las clases hijas a las clases superiores a fin de evitar este error? Ya que en esta estructura sólo podemos instanciar la clase mas baja del "árbol genealógico de clases" en el objeto bicicleta_electrica(). La solución es el uso de la instrucción SUPER()
La instrucción SUPER()
SUPER() es una instrucción que hace rerefencia a la clase padre que la contiene, con lo cual se puede desde una cLase hija, acceder a las propiedades y métodos que deseemos de la clase padre. En este caso nos vamos a valer de la instrucción SUPER() para pasarle desde la clase hija v_electrico() a la clase padre vehiculo() los valores para los parámetros marca y modelo. En este caso en el constructor de la clase hija vamos a incluir un llamado al constructor de la clase padre con los argumentos requeridos por éste. Esto se ve mas claro en el código a continuación:
import os
os.system('cls')
class vehiculos():
def __init__(self,marca,modelo) -> None:
self.marca=marca
self.modelo=modelo
self.rodando=False
self.acelerando=False
self.frenando=False
def encender(self):
self.rodando=True
def acelerar(self):
self.acelerando=True
def frenar(self):
self.frenando=True
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando)
class v_electricos(vehiculos):
def __init__(self) -> None:
super().__init__('BMW','Cruise E-bike')
self.autonomia = 100
def cargar_energia(self):
self.carga_electrica=True
class moto(vehiculos):
inclinacion='Neutral'
def inclinar(self,direccion):
self.inclinacion=direccion
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nInclinación: ',self.inclinacion)
class camioneta(vehiculos):
cargado=False
def carga(self,cargar):
self.cargado=cargar
if self.cargado:
return 'Camioneta cargada'
else:
return 'Camioneta vacía'
def estado(self):
print('********** DATOS DEL VEHICULO **********')
print('Marca: ', self.marca, '\nModelo: ',self.modelo,'\nAcelerando: ',\
self.acelerando, '\nFrenando: ', self.frenando, '\nRodando: ',\
self.rodando, '\nCargado: ',self.cargado, '\nEstado de la carga: ',\
self.carga(self.cargado))
class bicicleta_electrica(v_electricos):
pass
bicicleta_electrica01 = bicicleta_electrica()
bicicleta_electrica01.estado()
Resultado:
********** DATOS DEL VEHICULO **********
Marca: BMW
Modelo: Cruise E-bike
Acelerando: False
Frenando: False
Rodando: False
Como podemos comprobar el sistema de clases heredadas esta funcionando correctamente. Aún así esta no es la solución óptima ya que se está predefiniendo las mismas marcas y modelos para todas las clases vehiculos() padre que se hereden desde la clase hija v_electricos(). Para mejorar este sistema de clases es mejor cambiar el enfoque y establecer la jerarquía clases vehiculos() padre de v_electricos() y así los objetos instanciados de velectricos() ya heredarán las propiedades y métodos directamente de ambos, sin la necesidad del uso de una subclase nieta bicicleta_electrica(). (A menos que se deseen establecer propiedades y métodos específicos para bicicletas electricas a diferencia de otros vehículos electricos, pero este no es el caso en este ejemplo). Adicionalmente estamos dejando fijos los valores marca y modelo en el llamado al constructor de la superclase y esto no es funcional, ya que obviamente las marcas y modelos pueden variar de un vehículo a otro. Sumado al hecho que el método estado() de la superclase vehiculos() no está mostrando los datos relacionados a la clase hija. Para simplificar el código vamos a eliminar las otras clases definidas que no son relevantes para aclarar el punto del uso de la instrucción SUPER(). El código quedaría como sigue a continuación:
import os
os.system('cls')
class vehiculos():
def __init__(self,marca,modelo) -> None:
self.marca=marca
self.modelo=modelo
def estado(self):
print(f'''
************************************* DATOS DEL VEHICULO *************************************
** Marca: {self.marca}
** Modelo: {self.modelo}
**********************************************************************************************
''')
class v_electricos(vehiculos):
carga_electrica = False
def __init__(self,carga_electrica,autonomia,marca,modelo) -> None:
super().__init__(marca,modelo)
self.carga_electrica=carga_electrica
self.autonomia=autonomia
def cargar_energia(self):
if self.carga_electrica:
print('Batería ya esta al tope de carga.')
else:
print('Cargando batería...')
self.carga_electrica = True
print('Carga completada')
def estado(self):
super().estado()
print(f'''** Status carga electrica: {self.carga_electrica}
** Autonomía: {self.autonomia}
''')
bicicleta_electrica_01= v_electricos(False,100,'BMW','Cruise E-bike')
bicicleta_electrica_01.estado()
Resultado:
************************************* DATOS DEL VEHICULO *************************************
** Marca: BMW
** Modelo: Cruise E-bike
**********************************************************************************************
** Status carga electrica: False
** Autonomía: 100
Como podemos ver en el código ahora la clase v_electricos() es una subclase de vehiculos y al ser instanciada esta subclase, requiere le sean pasados los argumentos tanto de su clase como los de la clase padre, para que, usando la instrucción super() le sean enviados a su vez los argumentos requeridos por el constructor de la clase padre. También hemos hecho uso de la instrucción super en el método estado de la clase hija, invocando al método de la clase padre ahorrando tener que escribir todo el código que ya estaba en dicha clase, bastando con incluir solamente la instrucciones print() para incluir adicionalmente por pantalla los datos relacionados a la clase hija.
Función ISINSTANCE()
A medida que avanzamos en la construcción de nuestro programa en Python, se puede ir compicando el sistema de clases y herencia, es posible que en algún punto no recordemos bien qué clase hereda de cuál, o cuál clase es hija o padre de quién. Como norma general es recomendable preguntarse si una clase pertenece a otra clase y viceversa y así siempre tener clara la jerarquía de clases, tanto en la vida real como en Python. Por ejemplo cabría preguntarse ¿Todos los vehiculos eléctricos son vehículos? pues por definición misma sí lo es, pero en cambio no todos los vehículos son eléctricos.
En Python existe una función llamada ISINSTANCE() la cual compara un objeto creado y una clase, devolviendo TRUE o FALSE dependiendo si dicho objeto es una instancia de la clase enviada como argumento. Veamos su funcionamiento en el código de ejemplo de los vehículos electricos que venimos trabajando:
import os
os.system('cls')
class vehiculos():
def __init__(self,marca,modelo) -> None:
self.marca=marca
self.modelo=modelo
def estado(self):
print(f'''
************************************* DATOS DEL VEHICULO *************************************
** Marca: {self.marca}
** Modelo: {self.modelo}
**********************************************************************************************
''')
class v_electricos(vehiculos):
carga_electrica = False
def __init__(self,carga_electrica,autonomia,marca,modelo) -> None:
super().__init__(marca,modelo)
self.carga_electrica=carga_electrica
self.autonomia=autonomia
def cargar_energia(self):
if self.carga_electrica:
print('Batería ya esta al tope de carga.')
else:
print('Cargando batería...')
self.carga_electrica = True
print('Carga completada')
def estado(self):
super().estado()
print(f'''
** Status carga electrica: {self.carga_electrica}
** Autonomía: {self.autonomia}
''')
bicicleta_electrica_01= v_electricos(False,100,'BMW','Cruise E-bike')
bicicleta_electrica_01.estado()
auto01=vehiculos('Ford','Focus')
auto01.estado()
print('************************************** Relación de clases ************************************')
print(f'''
1.- ¿Es el vehículo bicicleta_electrica_01 instancia de la clase v_electricos()?
Respuesta: {isinstance(bicicleta_electrica_01,v_electricos)}
2.- ¿Es el vehículo bicicleta_electrica_01 instancia de la clase vehiculos()?
Respuesta: {isinstance(bicicleta_electrica_01,vehiculos)}
3.- ¿Es el vehículo auto01 instancia de la clase v_electricos()?
Respuesta: {isinstance(auto01,v_electricos)}
4.- ¿Es el vehículo auto01 instancia de la case vehiculos()?
Respuesta: {isinstance(auto01,vehiculos)}
''')
Resultado:
************************************* DATOS DEL VEHICULO *************************************
** Marca: BMW
** Modelo: Cruise E-bike
**********************************************************************************************
** Status carga electrica: False
** Autonomía: 100
************************************* DATOS DEL VEHICULO *************************************
** Marca: Ford
** Modelo: Focus
**********************************************************************************************
************************************** Relación de clases ************************************
1.- ¿Es el vehículo bicicleta_electrica_01 instancia de la clase v_electricos()?
Respuesta: True
2.- ¿Es el vehículo bicicleta_electrica_01 instancia de la clase vehiculos()?
Respuesta: True
3.- ¿Es el vehículo auto01 instancia de la clase v_electricos()?
Respuesta: False
4.- ¿Es el vehículo auto01 instancia de la case vehiculos()?
Respuesta: True
De acuerdo al resultado podemos comprobar que al hacer uso de la función ISINSTANCE() en los objetos creados obtendremos TRUE o FALSE de acuerdo a su naturaeza. Nótese que al consultar por la pertenencia o no del objeto bicicleta_electrica_01 con respecto a las clases v_electricos() y vehiculos(), el resultado es siempre TRUE, ya que , a pesar de haber sido instanciada la clase v_electricos(), ésta es a su vez hija de la superclase vehiculos, con lo cual tambíen es instancia de ésta. Caso contrario con le objeto auto01, el cual ha sido instanciado solamente de la clase vehiculos(), con lo cual el resultado es FALSE al consultar si este objeto es instancia de la clase v_electricos().
Comentarios
Publicar un comentario