Herencia¶
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 y hacer programar mucho más óptimos.
Para ver su utilidad, en esta lección vamos a desarrollar un ejemplo. 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.
Ejemplo sin herencia¶
Hace muchos años me vi en la necesidad de diseñar una estructura para una tienda que vendía tres tipos de productos: adornos, alimentos y libros.
Todos los productos de la tienda tenían una serie de atributos comunes, como la referencia, el nombre, el pvp... pero algunos eran específicos de cada tipo.
Si partimos de una clase que contenga todos los atributos, quedaría más o menos así:
class Producto:
def __init__(self, referencia, tipo, nombre,
pvp, descripcion, productor=None,
distribuidor=None, isbn=None, autor=None):
self.referencia = referencia
self.tipo = tipo
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion
self.productor = productor
self.distribuidor = distribuidor
self.isbn = isbn
self.autor = autor
adorno = Producto('000A','ADORNO','Vaso Adornado',15,
'Vaso de porcelana con dibujos')
print(adorno)
print(adorno.tipo)
<__main__.Producto object at 0x0000026233AD72E8>
ADORNO
Obviamente esto es un despropósito, así que veamos como aproecharnos de la herencia para mejorar el planteamento.
Superclases¶
Así pues la idea de la herencia es identificar una clase base (la superclase) con los atriutos 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,pvp,descripcion):
self.referencia = referencia
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion
def __str__(self):
return f"REFERENCIA\t {self.referencia}\n" \
f"NOMBRE\t\t {self.nombre}\n" \
f"PVP\t\t {self.pvp}\n" \
f"DESCRIPCIÓN\t {self.descripcion}\n"
Subclases¶
Para heredar los atributos y métodos de una clase en otra sólo tenemos que pasarla entre paréntesis durante la definición:
class Adorno(Producto):
pass
adorno = Adorno(2034, "Vaso adornado", 15, "Vaso de porcelana")
print(adorno)
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana
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:
class Alimento(Producto):
productor = ""
distribuidor = ""
def __str__(self):
return f"REFERENCIA\t {self.referencia}\n" \
f"NOMBRE\t\t {self.nombre}\n" \
f"PVP\t\t {self.pvp}\n" \
f"DESCRIPCIÓN\t {self.descripcion}\n" \
f"PRODUCTOR\t\t {self.productor}\n" \
f"DISTRIBUIDOR\t\t {self.distribuidor}\n"
class Libro(Producto):
isbn = ""
autor = ""
def __str__(self):
return f"REFERENCIA\t {self.referencia}\n" \
f"NOMBRE\t\t {self.nombre}\n" \
f"PVP\t\t {self.pvp}\n" \
f"DESCRIPCIÓN\t {self.descripcion}\n" \
f"ISBN\t\t {self.isbn}\n" \
f"AUTOR\t\t {self.autor}\n"
Ahora para utilizarlas simplemente tendríamos que establecer los atributos después de crear los objetos:
alimento = Alimento(2035, "Botella de Aceite de Oliva", 5, "250 ML")
alimento.productor = "La Aceitera"
alimento.distribuidor = "Distribuciones SA"
print(alimento)
libro = Libro(2036, "Cocina Mediterránea",9, "Recetas sanas y buenas")
libro.isbn = "0-123456-78-9"
libro.autor = "Doña Juana"
print(libro)
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva
PVP 5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
REFERENCIA 2036
NOMBRE Cocina Mediterránea
PVP 9
DESCRIPCIÓN Recetas sanas y buenas
ISBN 0-123456-78-9
AUTOR Doña Juana
Luego en los ejercicios os mostraré como podemos sobreescribir el constructor de una forma eficiente para inicializar campos extra, por ahora veamos como trabajar con estos objetos de distintas clases de forma común.
Trabajando en conjunto¶
Gracias a la flexibilidad de Python podemos manejar objetos de distintas clases masivamente de una forma muy simple.
Vamos a empezar creando una lista con nuestros tres productos de subclases distintas:
productos = [adorno, alimento]
productos.append(libro)
print(productos)
[<__main__.Adorno at 0x14c58660940>,
<__main__.Alimento at 0x14c586608d0>,
<__main__.Libro at 0x14c58660978>]
Ahora si queremos recorrer todos los productos de la lista podemos usar un bucle for:
for producto in productos:
print(producto, "\n")
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva
PVP 5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
REFERENCIA 2036
NOMBRE Cocina Mediterránea
PVP 9
DESCRIPCIÓN Recetas sanas y buenas
ISBN 0-123456-78-9
AUTOR Doña Juana
También podemos acceder a los atributos, siempre que sean compartidos entre todos los objetos:
for producto in productos:
print(producto.referencia, producto.nombre)
2034 Vaso adornado
2035 Botella de Aceite de Oliva Extra
2036 Cocina Mediterránea
Si un objeto no tiene el atributo al que queremos acceder nos dará error:
for producto in productos:
print(producto.autor)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-36e9baf5c1cc> in <module>()
1 for producto in productos:
----> 2 print(producto.autor)
AttributeError: 'Adorno' object has no attribute 'autor'
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:
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, p.isbn)
2034 Vaso adornado
2035 Botella de Aceite de Oliva La Aceitera
2036 Cocina Mediterránea 0-123456-78-9
Polimorfismo¶
El polimorfismo es una propiedad de la herencia por la que objetos de distintas subclases pueden responder a una misma acción.
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:
def rebajar_producto(producto, rebaja):
producto.pvp = producto.pvp - (producto.pvp/100 * rebaja)
Gracias al polimorfismo no tenemos que comprobar si un objeto tiene o no el atributo pvp, simplemente intentamos acceder y si existe premio:
print(alimento, "\n")
rebajar_producto(alimento, 10)
print(alimento)
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva
PVP 5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
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.
Última edición: 29 de Septiembre de 2018