Clase 36 Python - Herencia y Polimorfismo
Clase 36 Python - Herencia y Polimorfismo
Clase 36 Python - Herencia y Polimorfismo
Fuente: hektorprofe.net/analyticslane.com/wikipedia.org
La herencia es la capacidad que tiene una clase de heredar los atributos y métodos de otra,
algo que nos permite reutilizar código.
Partiremos de una clase sin herencia con muchos atributos y la iremos descomponiendo en
otras clases más simples que nos permitan trabajar mejor con sus datos.
Si partimos de una clase que contenga todos los atributos, quedaría más o menos así:
Código
class Producto:
def __init__(self, referencia, tipo, nombre,
descripcion,precio,productor="",
distribuidor="", isbn="", autor=""):
self.referencia = referencia
self.tipo = tipo
self.nombre = nombre
self.descripcion = descripcion
self.precio = precio
self.productor = productor
self.distribuidor = distribuidor
self.isbn = isbn
self.autor = autor
Resultado
Superclases
Así pues, la idea de la herencia es identificar una clase base (la superclase) con los
atributos comunes y luego crear las demás clases heredando de ella (las subclases)
extendiendo sus campos específicos. En nuestro caso esa clase sería el Producto en sí
mismo.
class Producto:
def __init__(self,referencia,nombre,descripcion,precio):
self.referencia = referencia
self.nombre = nombre
self.descripcion = descripcion
self.precio = precio
def __str__(self):
return "Producto: {} - {} - {} -
{}".format(self.referencia,self.nombre,self.descripcion,self.precio)
def rebajar_producto(self,rebaja):
self.precio = self.precio - rebaja
Subclases
Para heredar los atributos y métodos de una clase en otra sólo tenemos que pasarla entre
paréntesis en la definición:
Se importará el contenido de otro archivo .py agregando en la cabecera del archivo from
(nombre del archivo sin la extensión .py) import (Clase a importar).
Como tener que determinar constantemente la superclase puede ser fastidioso, Python nos
permite utilizar un acceso directo mucho más cómodo llamado super().
Hacerlo de esta forma además nos permite llamar cómodamente los métodos o atributos de
la superclase sin necesidad de especificar el self, pero ojo, sólo se aconseja utilizarlo
cuando tenemos una única superclase:
Código
from producto import Producto
class Adorno(Producto):
def __init__(self,referencia,nombre,descripcion,precio,estilo):
super().__init__(referencia, nombre, descripcion,precio)
self.estilo = estilo
def __str__(self):
return "{} - {} - {} -
{}".format(self.referencia,self.nombre,self.descripcion,self.estilo)
Como se puede apreciar es posible utilizar el comportamiento de una superclase sin definir
nada en la subclase.
Respecto a las demás subclases como se añaden algunos atributos, podríamos definirlas
de esta forma:
from producto import Producto
class Libro(Producto):
def __init__(self,referencia,nombre,descripcion,precio,isbn,autor):
super().__init__(referencia, nombre, descripcion,precio)
self.isbn = isbn
self.autor = autor
def __str__(self):
return "{} - {} - {} - {} - {} -
{}".format(self.referencia,self.nombre,self.descripcion,self.precio,self.isbn,self.autor)
Ahora para utilizarlas simplemente tendríamos que establecer los atributos después de
crear los objetos:
• Otros lenguajes (como C y Java) tienen una función main() que se llama cuando se
ejecuta el programa. Utilizando este if, podemos hacer que Python se comporte
como ellos, lo cual es más familiar para muchas personas.
• El código será más fácil de leer y estará mejor organizado.
• Será posible ejecutar tests en el código.
• Podemos importar ese código en un shell de python y probarlo/depurarlo/ejecutarlo.
• variables dentro def main() son locales, mientras que las que están afuera
son globales. Esto puede introducir algunos errores y comportamientos
inesperados.
• Permite ejecutar la función si se importa el archivo como un módulo
Código (main.py)
from producto import Producto
from alimento import Alimento
from adorno import Adorno
from libro import Libro
def main():
producto = Producto(2033,"Producto Genérico","1 kg",50)
alimento = Alimento(2035, "Botella de Aceite de Oliva","250
ML",50,"Marca","Distribuidor")
adorno = Adorno(2034, "Vaso adornado", "Vaso de porcelana",34,"De Mesa")
libro = Libro(2036, "Cocina Mediterránea","Recetas buenas",75,"0-123456-78-
9","Autor")
if __name__ == "__main__":
main()
Trabajando en conjunto
Vamos a empezar creando una lista con nuestros tres productos de subclases distintas:
Código
productos = [adorno, alimento]
productos.append(libro)
Ahora si queremos recorrer todos los productos de la lista podemos usar un bucle for:
Código
También podemos acceder a los atributos, siempre que sean compartidos entre todos los
objetos:
Código
for producto in productos:
print(producto.referencia, producto.nombre)
Por suerte podemos hacer una comprobación con la función isinstance() para determinar si
una instancia es de una determinado clase y así mostrar unos atributos u otros:
Código
for producto in productos:
if(isinstance(producto, Adorno)):
print(producto.referencia, producto.nombre)
elif(isinstance(producto, Alimento)):
print(producto.referencia, producto.nombre, producto.productor)
elif(isinstance(producto, Libro)):
print(producto.referencia, producto.nombre, producto.isbn)
Polimorfismo
La polimorfia es implícita en Python, ya que todas las clases son subclases de una
superclase común llamada Object.
Por ejemplo la siguiente función aplica una rebaja al precio de un producto, ubicar en
producto.py:
def rebajar_producto(self,rebaja):
self.precio = self.precio - rebaja
Código
for producto in productos:
producto.rebajar_producto(10)
print(producto)
Por cierto, como podéis ver en el ejemplo, cuando modificamos un atributo de un objeto
dentro de una función éste cambia en la instancia. Esto es por aquello que os comenté del
paso por valor y referencia.
Herencia múltiple
En estos casos Python dará prioridad a las clases más a la izquierda en el momento de la
declaración de la subclase:
Código
(a.py)
class A:
def a(self):
print("Este método lo heredo de A")
def b(self):
print("Este método lo heredo de A")
(b.py)
class B:
def b(self):
print("Este método lo heredo de B")
(c.py)
from a import A
from b import B
class C(B,A):
def __init__(self):
print("Soy de la clase C")
def c(self):
print("Este método es de C")
(main.py)
from a import A
from b import B
from c import C
def main():
c = C()
c.a()
c.b()
c.c()
if __name__ == "__main__":
main()
Hasta ahora sabemos que una clase heredada puede fácilmente extender algunas
funcionalidades, simplemente añadiendo nuevos atributos y métodos, o sobreescribiendo
los ya existentes. Como en el siguiente ejemplo:
Ejercicio
class Vehiculo():
def __str__(self):
return "Color {}, {} ruedas".format(self.color,self.ruedas)
class Coche(Vehiculo):
def __str__(self):
return "color {}, {} km/h, {} ruedas, {} cc".format( self.color, self.velocidad,
self.ruedas, self.cilindrada )
def __str__(self):
return "color {}, {} ruedas".format(self.color, self.ruedas)
(coche.py)
class Coche(Vehiculo):
def __str__(self):
return Vehiculo.__str__(self) + ", {} km/h, {} cc".format(self.velocidad,
self.cilindrada)
(main.py)
from coche import Coche
def main():
c = Coche("azul", 4, 150, 1200)
print(c)
if __name__ == "__main__":
main()
Enunciado
Clases Abstractas
Otra característica de estas clases es que no es necesario que tengan una implementación
de todos los métodos necesarios. Pudiendo ser estos abstractos. Los métodos abstractos
son aquellos que solamente tienen una declaración, pero no una implementación detallada
de las funcionalidades.
Las clases derivadas de las clases abstractas debe implementar necesariamente todos los
métodos abstractos para poder crear una clase que se ajuste a la interfaz definida. En el
caso de que no se defina alguno de los métodos no se podrá crear la clase.
Resumiendo, las clases abstractas definen una interfaz común para las subclases.
Proporcionan atributos y métodos comunes para todas las subclases evitando así la
necesidad de duplicar código. Imponiendo además los métodos que deber ser
implementados para evitar inconsistencias entre las subclases
class Animal(ABC):
@abstractmethod
def mover(self):
pass
Ahora si se intenta crear una instancia de la clase animal, Python no lo permitirá indicando
que no es posible. Es importante notar que, si la clase no hereda de ABC o contiene por lo
menos un método abstracto, Python permitirá instancias de las clases.
class Animal(ABC):
def mover(self):
print("El animal se mueve")
animal = Animal()
class Animal(ABC):
@abstractmethod
def mover(self):
pass
@abstractmethod
def comer(self):
print('El animal come')
Por otro lado, desde los métodos de las subclases podemos llamar a las implementaciones
de la clase abstracta con el comando super() seguido del nombre del método. La palabra
pass permite no definir el contenido de un método.
(animal.py)
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def mover(self):
pass
@abstractmethod
def comer(self):
print('Animal come')
(gato.py)
from animal import Animal
class Gato(Animal):
def mover(self):
print('Mover gato')
def comer(self):
super().comer()
print('Gato come')
(main.py)
from gato import Gato
def main():
g = Gato()
g.mover()
g.comer()
if __name__ == "__main__":
main()
Copia de Objetos
Para realizar una copia a partir de sus valores podemos utilizar la función copy del módulo
con el mismo nombre:
class Test:
pass
test1 = Test()
test2 = copy(test1)
Diagrama de Clases
+ Público
- Privado
# Protegido
Ámbitos
UML especifica dos tipos de ámbitos para los miembros: instancias y clasificadores y estos
últimos se representan con nombres subrayados.
● Los miembros clasificadores se denotan comúnmente como “estáticos” en muchos
lenguajes de programación. Su ámbito es la propia clase.
○ Los valores de los atributos son los mismos en todas las instancias
○ La invocación de métodos no afecta al estado de las instancias
Agregación
Como se puede ver en la imagen del ejemplo (en inglés), un Profesor ‘tiene una o más clases’
clase a las que enseña.
Una agregación puede tener un nombre e indicaciones de cardinalidad en los extremos de la
línea. Sin embargo, una agregación no puede incluir más de dos clases; debe ser una
asociación binaria.
Una agregación se puede dar cuando una clase es una colección o un contenedor de otras
clases, pero a su vez, el tiempo de vida de las clases contenidas no tienen una dependencia
fuerte del tiempo de vida de la clase contenedora. Es decir, el contenido de la clase
contenedora no se destruye automáticamente cuando desaparece dicha clase.
Se representa gráficamente con un rombo hueco junto a la clase contenedora con una línea
que lo conecta a la clase contenida. Todo este conjunto es, semánticamente, un objeto
extendido que es tratado como una única unidad en muchas operaciones, aunque físicamente
está hecho de varios objetos más pequeños.
Composición
Relación de Composición
Relación de Agregación