Herencias
Estructura para los productos de una tienda
Ejemplo sin herencia
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)
<__main__.Producto at 0x5243810>
adorno.tipo
'ADORNO'
Creando una jerarquía de productos con clases
Superclase Producto
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 """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion)
Subclase Adorno
class Adorno(Producto):
pass
a = Adorno(2034,"Vaso adornado",15,"Vaso de porcelana adornado con árboles")
print(a)
Referencias 2034
Nombre Vaso adornado
PVP 15
Descripcion Vaso de porcelana adornado con árboles
Subclase Alimento
class Alimento(Producto):
productor = ""
distribuidor = ""
def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}
PRODUCTOR\t{}
DISTRIBUIDOR\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion,self.productor,self.distribuidor)
al = Alimento(2035,"Botella de Aceite de Oliva Extra",5,"250 ML")
al.productor = "La Aceitera"
al.distribuidor = "Distribuciones SA"
print(al)
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva Extra
PVP 5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
Subclase Libro
class Libro(Producto):
isbn = ""
autor = ""
def __str__(self):
return """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}
ISBN\t\t{}
AUTOR\t\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion,self.isbn,self.autor)
li = Libro(2036,"Cocina Mediterránea",9,"Recetas sanas y buenas")
li.isbn = "0-123456-78-9"
li.autor = "Doña Juana"
print(li)
REFERENCIA 2036
NOMBRE Cocina Mediterránea
PVP 9
DESCRIPCIÓN Recetas sanas y buenas
ISBN 0-123456-78-9
AUTOR Doña Juana
Trabajando con clases heredadas en conjunto
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 """\
REFERENCIA\t{}
NOMBRE\t\t{}
PVP\t\t{}
DESCRIPCIÓN\t{}""".format(self.referencia,self.nombre,self.pvp,self.descripcion)
class Adorno(Producto):
pass
class Alimento(Producto):
productor = ""
distribuidor = ""
def __str__(self):
return super().__str__() + """\
PRODUCTOR\t{}
DISTRIBUIDOR\t{}""".format(self.productor,self.distribuidor)
class Libro(Producto):
isbn = ""
autor = ""
def __str__(self):
return super().__str__() + """\
ISBN\t\t{}
AUTOR\t\t{}""".format(self.isbn,self.autor)
Creamos algunos objetos
ad = Adorno(2034,"Vaso adornado",15,"Vaso de porcelana adornado con árboles")
al = Alimento(2035,"Botella de Aceite de Oliva Extra",5,"250 ML")
al.productor = "La Aceitera"
al.distribuidor = "Distribuciones SA"
li = Libro(2036,"Cocina Mediterránea",9,"Recetas sanas y buenas")
li.isbn = "0-123456-78-9"
li.autor = "Doña Juana"
Lista de productos
productos = [ad, al]
productos.append(li)
print(productos)
[<__main__.Adorno at 0x14c58660940>,
<__main__.Alimento at 0x14c586608d0>,
<__main__.Libro at 0x14c58660978>]
Lectura secuencial de productos con un for .. in
for p in productos:
print(p,"\n")
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana adornado con árboles
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva Extra
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
Podemos acceder a los atributos si son compartidos entre todos los objetos
for p in productos:
print(p.referencia, p.nombre)
2034 Vaso adornado
2035 Botella de Aceite de Oliva Extra
2036 Cocina Mediterránea
Pero si un objeto no tiene el atributo deseado, dará error:
for p in productos:
print(p.autor)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-36e9baf5c1cc> in <module>()
1 for p in productos:
----> 2 print(p.autor)
AttributeError: 'Adorno' object has no attribute 'autor'
Tendremos que tratar cada subclase de forma distinta, gracias a la función isistance():
for p in productos:
if( isinstance(p, Adorno) ):
print(p.referencia,p.nombre)
elif( isinstance(p, Alimento) ):
print(p.referencia,p.nombre,p.productor)
elif( isinstance(p, Libro) ):
print(p.referencia,p.nombre,p.isbn)
2034 Vaso adornado
2035 Botella de Aceite de Oliva Extra La Aceitera
2036 Cocina Mediterránea 0-123456-78-9
Funciones que reciben objetos de distintas clases
Los obetos se envían por referencia a las funciones
Así que debemos tener en cuenta que cualquier cambio realizado dentro afectará al propio objeto.
def rebajar_producto(p, rebaja):
"""Rebaja un producto en porcentaje de su precio"""
p.pvp = p.pvp - (p.pvp/100 * rebaja)
rebajar_producto(al, 10)
print(al_rebajado)
REFERENCIA 2038
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
print(al)
REFERENCIA 2035
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
Una copia de un objeto también hace referencia al objeto copiado (como un acceso directo)
copia_al = al
copia_al.referencia = 2038
print(copia_al)
REFERENCIA 2038
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
print(al)
REFERENCIA 2038
NOMBRE Botella de Aceite de Oliva Extra
PVP 4.5
DESCRIPCIÓN 250 ML
PRODUCTOR La Aceitera
DISTRIBUIDOR Distribuciones SA
Esto también sucede con los tipos compuestos:
l = [1,2,3]
l2 = l[:]
l2.append(4)
l
[1, 2, 3, 4]
Para crear una copia 100% nueva debemos utilizar el módulo copy:
import copy
copia_ad = copy.copy(ad)
print(copia_ad)
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana adornado con árboles
copia_ad.pvp = 25
print(copia_ad)
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 25
DESCRIPCIÓN Vaso de porcelana adornado con árboles
print(ad)
REFERENCIA 2034
NOMBRE Vaso adornado
PVP 15
DESCRIPCIÓN Vaso de porcelana adornado con árboles
Polimorfismo
Se refiere a una propiedad de la herencia por la que objetos de distintas subclases pueden responder a una misma acción.
def rebajar_producto(p, rebaja):
p.pvp = p.pvp - (p.pvp/100 * rebaja)
El método rebajar_producto() es capaz de tomar objetos de distintas subclases y manipular el atributo pvp.
La acción de manipular el pvp funcionará siempre que los objetos tengan ése atributo, pero en el caso de no ser así, daría error.
La polimorfia es implícita en Python en todos los objetos, ya que todos son hijos de una superclase común llamada Object.
Herencia múltiple
Posibilidad de que una subclase herede de múltiples superclases.
El problema aparece cuando las superclases tienen atributos o métodos comunes.
En estos casos, Python dará prioridad a las clases más a la izquierda en el momento de la declaración de la subclase.
class A:
def __init__(self):
print("Soy de clase A")
def a(self):
print("Este método lo heredo de A")
class B:
def __init__(self):
print("Soy de clase B")
def b(self):
print("Este método lo heredo de B")
class C(B,A):
def c(self):
print("Este método es de C")
c = C()
Soy de clase B
c.a()
Este método lo heredo de A
c.b()
Este método lo heredo de B
c.c()
Este método es de C
Ejercicio: Herencia en la POO
En este ejercicio vas a trabajar el concepto de herencia un poco más en profundidad, aprovechando para introducir un nuevo concepto muy importante que te facilitará mucho la vida.
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
class Vehiculo():
def __init__(self, color, ruedas):
self.color = color
self.ruedas = ruedas
def __str__(self):
return "color {}, {} ruedas".format( self.color, self.ruedas )
class Coche(Vehiculo):
def __init__(self, color, ruedas, velocidad, cilindrada):
self.color = color
self.ruedas = ruedas
self.velocidad = velocidad
self.cilindrada = cilindrada
def __str__(self):
return "color {}, {} km/h, {} ruedas, {} cc".format( self.color, self.velocidad, self.ruedas, self.cilindrada )
c = Coche("azul", 150, 4, 1200)
print(c)
Color azul, 4 km/h, 150 ruedas, 1200 cc
El inconveniente más evidente de ir sobreescribiendo es que tenemos que volver a escribir el código de la superclase y luego el específico de la subclase.
Para evitarnos escribir código innecesario, podemos utilizar un truco que consiste en llamar el método de la superclase y luego simplemente escribir el código de la clase:
class Vehiculo():
def __init__(self, color, ruedas):
self.color = color
self.ruedas = ruedas
def __str__(self):
return "color {}, {} ruedas".format( self.color, self.ruedas )
class Coche(Vehiculo):
def __init__(self, color, ruedas, velocidad, cilindrada):
Vehiculo.__init__(self, color, ruedas)
self.velocidad = velocidad
self.cilindrada = cilindrada
def __str__(self):
return Vehiculo.__str__(self) + ", {} km/h, {} cc".format(self.velocidad, self.cilindrada) # utilizamos super()
c = Coche("azul", 4, 150, 1200)
print(c)
Color azul, 4 ruedas, 150 km/h, 1200 cc
Como tener que determinar constantemente la superclase puede ser fastidioso, Python nos permite utilizar un acceso directo mucho más cómodo llamada 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.
class Vehiculo():
def __init__(self, color, ruedas):
self.color = color
self.ruedas = ruedas
def __str__(self):
return "color {}, {} ruedas".format( self.color, self.ruedas )
class Coche(Vehiculo):
def __init__(self, color, ruedas, velocidad, cilindrada):
super().__init__(color, ruedas) # utilizamos super() sin self en lugar de Vehiculo
self.velocidad = velocidad
self.cilindrada = cilindrada
def __str__(self):
return super().__str__() + ", {} km/h, {} cc".format(self.velocidad, self.cilindrada)
c = Coche("azul", 4, 150, 1200)
print(c)
Vehículo azul, 4 ruedas, 150 km/h, 1200 cc
Ejercicio
Utilizando esta nueva técnica, extiende la clase Vehiculo y realiza la siguiente implementación:
Experimenta
- Crea al menos un objeto de cada subclase y añádelos a una lista llamada vehiculos.
- Realiza una función llamada catalogar() que reciba la lista de vehiculos y los recorra mostrando el nombre de su clase y sus atributos.
- Modifica la función catalogar() para que reciba un argumento optativo ruedas, haciendo que muestre únicamente los que su número de ruedas concuerde con el valor del argumento. También debe mostrar un mensaje "Se han encontrado {} vehículos con {} ruedas:" únicamente si se envía el argumento ruedas. Ponla a prueba con 0, 2 y 4 ruedas como valor.
Recordatorio: Puedes utilizar el atributo especial de clase name de la siguiente forma para recuperar el nombre de la clase de un objeto:
type(objeto).__name__
class Vehiculo():
def __init__(self, color, ruedas):
self.color = color
self.ruedas = ruedas
def __str__(self):
return "color {}, {} ruedas".format( self.color, self.ruedas )
class Coche(Vehiculo):
def __init__(self, color, ruedas, velocidad, cilindrada):
super().__init__(color, ruedas) # utilizamos super() sin self en lugar de Vehiculo
self.velocidad = velocidad
self.cilindrada = cilindrada
def __str__(self):
return super().__str__() + ", {} km/h, {} cc".format(self.velocidad, self.cilindrada)
# Completa el ejercicio aquí
class Camioneta(Coche):
def __init__(self, color, ruedas, velocidad, cilindrada, carga):
super().__init__(color, ruedas, velocidad, cilindrada)
self.carga = carga
def __str__(self):
return super().__str__() + ", {} kg de carga".format(self.carga)
class Bicicleta(Vehiculo):
def __init__(self, color, ruedas, tipo):
super().__init__(color, ruedas)
self.tipo = tipo
def __str__(self):
return super().__str__() + ", {}".format(self.tipo)
class Motocicleta(Bicicleta):
def __init__(self, color, ruedas, tipo, velocidad, cilindrada):
super().__init__(color, ruedas, tipo)
self.velocidad = velocidad
self.cilindrada = cilindrada
def __str__(self):
return super().__str__() + ", {} km/h, {} cc".format(self.velocidad, self.cilindrada)
def catalogar(vehiculos):
for v in vehiculos:
print(type(v).__name__, v)
def catalogar(vehiculos, ruedas=None):
# Primera pasada, mostrar recuento
if ruedas != None:
contador = 0
for v in vehiculos:
if v.ruedas == ruedas:
contador += 1
print("\nSe han encontrado {} vehículos con {} ruedas:".format(contador, ruedas))
# Segnda pasada, mostrar vehículos
for v in vehiculos:
if ruedas == None:
print(type(v).__name__, v)
else:
if v.ruedas == ruedas:
print(type(v).__name__, v)
lista = [
Coche("azul", 4, 150, 1200),
Camioneta("blanco", 4, 100, 1300, 1500),
Bicicleta("verde", 2, "urbana"),
Motocicleta("negro", 2, "deportiva", 180, 900)
]
catalogar(lista)
catalogar(lista, 0)
catalogar(lista, 2)
catalogar(lista, 4)
Coche color azul, 4 ruedas, 150 km/h, 1200 cc
Camioneta color blanco, 4 ruedas, 100 km/h, 1300 cc, 1500 kg de carga
Bicicleta color verde, 2 ruedas, urbana
Motocicleta color negro, 2 ruedas, deportiva, 180 km/h, 900 cc
Se han encontrado 0 vehículos con 0 ruedas:
Se han encontrado 2 vehículos con 2 ruedas:
Bicicleta color verde, 2 ruedas, urbana
Motocicleta color negro, 2 ruedas, deportiva, 180 km/h, 900 cc
Se han encontrado 2 vehículos con 4 ruedas:
Coche color azul, 4 ruedas, 150 km/h, 1200 cc
Camioneta color blanco, 4 ruedas, 100 km/h, 1300 cc, 1500 kg de carga