100% encontró este documento útil (1 voto)
135 vistas25 páginas

Herencia en Python: Curso FullStack

Este documento explica el concepto de herencia en Python. La herencia permite que una clase herede los atributos y métodos de otra clase padre, permitiendo reutilizar código. Se presenta un ejemplo de una tienda con diferentes tipos de productos y cómo refactorizarlo usando herencia para crear una clase Producto base y subclases específicas para cada tipo. También se explica el uso de superclases, subclases y cómo crear objetos a partir de estas para ilustrar la herencia entre vehículos y coches.

Cargado por

furia bebe
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
100% encontró este documento útil (1 voto)
135 vistas25 páginas

Herencia en Python: Curso FullStack

Este documento explica el concepto de herencia en Python. La herencia permite que una clase herede los atributos y métodos de otra clase padre, permitiendo reutilizar código. Se presenta un ejemplo de una tienda con diferentes tipos de productos y cómo refactorizarlo usando herencia para crear una clase Producto base y subclases específicas para cada tipo. También se explica el uso de superclases, subclases y cómo crear objetos a partir de estas para ilustrar la herencia entre vehículos y coches.

Cargado por

furia bebe
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Curso FullStack

Python
Codo a Codo 4.0
Python
Parte 7
Herencia
La herencia es la capacidad que tiene una clase de heredar los atributos y métodos de otra, ya
preexistente, 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.
Ejemplo sin herencia
Una tienda vende tres tipos de productos: adornos, alimentos y libros. Todos los productos de la
tienda tienen una serie de atributos comunes, como la referencia, el nombre, el precio... pero
algunos son 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,
descripcion, precio, productor="",
distribuidor="", isbn="", autor=""): Obviamente esto es
[Link] = referencia #Atributo común un despropósito, así
[Link] = tipo #Atributo común que veamos cómo
[Link] = nombre #Atributo común aprovecharnos de la
[Link] = descripcion #Atributo común herencia para
[Link] = precio #Atributo común
mejorar el
[Link] = productor #Atributo de alimento
planteamiento.
[Link] = distribuidor #Atributo de alimento
[Link] = isbn #Atributo de libro
[Link] = autor #Atributo de libro
Superclases
Una superclase es una clase superior o clase base/padre. Dijimos que las clases eran moldes para
construir objetos, las superclases son “moldes de moldes”.
A partir de la clase superior puedo construir clases hijas, clases derivadas, cada una con
características comunes que comparten atributos con la clase padre, pero que también pueden
tener atributos propios.
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):
[Link] = referencia
[Link] = nombre
[Link] = descripcion
[Link] = precio

def __str__(self):
[Link]
return "Producto: {} - {} - {} - {}".format([Link],[Link]
e,[Link],[Link])
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", "Vaso de porcelana", 15)


print(adorno)

Producto: 2034 - Vaso adornado - Vaso de porcelana – 15 terminal

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 "Referencia: {} \nNombre: {} \nDescripción: {} \nPrecio: {} \
nProductor: {} \nDistribuidor: {}".format([Link], [Link], self.
descripcion, [Link], [Link], [Link])

class Libro(Producto):
isbn = ""
autor = ""

def __str__(self):
return "Referencia: {} \nNombre: {} \nDescripción: {} \nPrecio: {} \
nISBN: {} \nAutor: {}".format([Link], [Link], [Link],
[Link], [Link], [Link])
Ahora para utilizarlas simplemente tendríamos que establecer los atributos después de
crear los objetos:
#Programa principal
alimento = Alimento(2035, "Botella de Aceite de Oliva", "250 ML", 5)
[Link] = "La Aceitera"
[Link] = "Distribuciones SA"
print(alimento)

libro = Libro(2036, "Cocina Mediterránea","Recetas sanas y buenas", 9)


[Link] = "0-123456-78-9"
[Link] = "Doña Juana" Referencia: 2035 terminal
print(libro) Nombre: Botella de Aceite de Oliva
Descripción: 250 ML
Precio: 5
Productor: La Aceitera
Distribuidor: Distribuciones SA
Referencia: 2036
Nombre: Cocina Mediterránea
Descripción: Recetas sanas y buenas
Precio: 9
ISBN: 0-123456-78-9
Herencia – Ejemplo: Vehículos y coches
Debemos comenzar por la clase más abstracta.
Vehículo: es una clase superior (superclase) y Coche es una clase concreta (subclase).
Al tener herencia debemos partir de la clase más abstracta:
Vehículo
class Vehiculo():
color
def __init__(self, color, ruedas):
ruedas
[Link]= color
[Link]= ruedas

Creamos la instancia del objeto (constructor, junto


con self y los atributos).
Recordemos que el self es para hacer referencia al propio objeto. Cuando lo Auto
instancie, cuando llamo al método le indico que estoy hablando de esa
única instancia de objeto creada a partir de la clase. velocidad (km/h)
def __str__(self):
return "Color: {} - Ruedas: {}".format([Link], [Link]) La flecha indica la
Recordemos que __str__ retorna una cadena, que la vamos formando con jerarquía y la relación
las llaves y el format. Esa cadena devuelve información de ese objeto. de herencia.

Herencia_polimorfi[Link]
Para que herede debemos poner class claseprincipal(clasequehereda). Por ejemplo:

class Auto(Vehiculo): Auto heredará de Vehículo

Dentro de la clase auto que deriva de la clase vehículo tenemos el __init__ que es el constructor
de esa clase heredada, que va a tener los atributos de la superclase (color, ruedas) y los atributos
propios (velocidad).
def __init__(self, color, ruedas, velocidad): Llamada al método
Vehiculo.__init__(self, color, ruedas) constructor de la
[Link]= velocidad superclase

Constructor de la clase auto: Se colocan color y ruedas porque


derivan de la clase vehículo
La llamada al método constructor de la superclase Vehículo la podemos reemplazar por:
super().__init__(color, ruedas)
def __init__(self, color, ruedas, velocidad):
super().__init__(color, ruedas)
[Link]= velocidad
Luego sobreescribimos el método __str__ agregándole la superclase y adjuntándole la velocidad:
def __str__(self):
return super().__str__() + " - Velocidad: " + str([Link])
En el programa principal podemos crear los objetos Auto a partir de la superclase Vehiculo:
# Programa principal
v1= Vehiculo("Blanco",2)
a1= Auto("Rojo",4,140)
a2= Auto("Negro",4,180)
a3= Auto(“Azul",4,200)

print(v1) Color: Blanco - Ruedas: 2 terminal


print(a1) Color: Rojo - Ruedas: 4 - Velocidad: 140
print(a2)
Color: Negro - Ruedas: 4 - Velocidad: 180
print(a3)
Color: Azul - Ruedas: 4 - Velocidad: 200

También podríamos agregar los vehículos y autos a una lista de objetos:


vehiculos= [] Color: Blanco - Ruedas: 2 terminal
[Link](v1) Color: Negro - Ruedas: 4 - Velocidad: 180
[Link](a1)
[Link](a2) Los imprime distinto, pero ¿cómo se da cuenta de qué tipo de clase es?
[Link](a3) Acá empieza a ser importante el Polimorfismo.

print(vehiculos[0]) Otro ejemplo de herencia:


print(vehiculos[2]) [Link]
-ejemplos-oop/
Definición de la función main()
Cuando el intérprete lee un archivo de código, ejecuta todo el código global que se encuentra en
él. Esto implica crear objetos para toda función o clase definida y variables globales.
Todo módulo (archivo de código) en Python tiene un atributo especial llamado __name__ que
define el espacio de nombres en el que se está ejecutando. Es usado para identificar de forma
única un módulo en el sistema de importaciones.
Por su parte __main__ es el nombre del ámbito en el que se ejecuta el código de nivel superior (tu
programa principal).
El intérprete pasa el valor del atributo __name__ a la cadena __main__ si el módulo se está
ejecutando como programa principal (cuando lo ejecutas llamando al intérprete en la terminal
con python my_modulo.py, haciendo doble clic en él, ejecutándolo en el intérprete interactivo,
etc.).
Si el módulo no es llamado como programa principal, sino que es importado desde otro módulo,
el atributo __name__ pasa a contener el nombre del archivo en sí.

Ver carpeta definicion_main y archivo [Link]


Ventajas de usar def main()
• El código será más fácil de leer y estará mejor organizado.
• Será posible ejecutar pruebas 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.
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
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 cuatro productos de subclases distintas:
productos = [producto,alimento,adorno]
[Link](libro)
print(productos)

[<[Link] object at 0x000001F073856BB0>, terminal


<[Link] object at 0x000001F073856AC0>,
<[Link] object at 0x000001F73856970>,
<[Link] object at 0x000001F073856940>]
Ahora si queremos recorrer todos los productos de la lista podemos usar un bucle for:
print("Recorriendo lista de productos:") Recorriendo lista de productos: terminal
for producto in productos: Producto: 2033 - Producto Genérico - 1 kg - 50
print(producto) Alimento: 2035 - Botella de Aceite de Oliva - 250 ML - 50
- Marca - Distribuidor
Adorno: 2034 - Vaso adornado - Vaso de porcelana - 34 -
De Mesa
Libro: 2036 - Cocina Mediterránea - Recetas buenas - 75 -
0-123456-78-9 - Autor
También podemos acceder a los atributos, siempre que sean compartidos entre todos los objetos:

print("Recorriendo lista de productos (atributos):") 2033 Producto Genérico terminal


for producto in productos: 2035 Botella de Aceite de Oliva
print([Link], [Link])
2034 Vaso adornado
2036 Cocina Mediterránea

Si un objeto no tiene el atributo al que queremos acceder nos dará error:


for producto in productos: AttributeError: 'Producto' object
print([Link]) has no attribute 'autor' terminal

Por suerte podemos hacer una comprobación con la función isinstance() para determinar si una
instancia es de una determinada clase y así mostrar unos atributos u otros:
print("Condicional isInstance():")
for producto_r in productos:
if(isinstance(producto_r, Adorno)):
terminal
print(producto_r.referencia, producto_r.nombre, producto_r.estilo)
elif(isinstance(producto_r, Alimento)):
print(producto_r.referencia, producto_r.nombre, producto_r.productor)
elif(isinstance(producto_r, Libro)):
print(producto_r.referencia, producto_r.nombre, producto_r.isbn)
Aunque esta no será la forma que utilizaremos a futuro ya que nos valdremos del Poliformismo
Polimorfismo
El polimorfismo es uno de los pilares básicos en la programación orientada a objetos, tiene origen
en las palabras poly (muchos) y morfo (formas), y aplicado a la programación hace referencia a
que los objetos pueden tomar diferentes formas.
Esto significa que los objetos de diferentes clases pueden ser accedidos utilizando el mismo
interfaz, mostrando un comportamiento distinto (tomando diferentes formas) según cómo sean
accedidos.
En lenguajes de programación como Python, que tiene tipado dinámico, el polimorfismo va muy
relacionado con el duck typing.
Dado que Python es de tipado dinámico y permite duck typing no es necesario que los objetos
compartan un interfaz, simplemente basta con que tengan los métodos que se quieren llamar.

Ejemplo:
Supongamos que tenemos una clase Animal con un método hablar() y por otro lado tenemos
otras dos clases, Perro, Gato que heredan de la anterior. Además, implementan el método hablar()
de una forma distinta.
class Animal:
def hablar(self):
pass
class Gato(Animal): class Perro(Animal):
def hablar(self): def hablar(self):
print("Miau!") print("Guau!")
A continuación creamos un objeto de cada clase y llamamos al método hablar(). Podemos
observar que cada animal se comporta de manera distinta al usar hablar().

for animal in Perro(), Gato(): Guau! terminal


[Link]() Miau!

En el caso anterior, la variable animal ha ido “tomando las formas” de Perro y Gato. Sin
embargo, nótese que al tener tipado dinámico este ejemplo hubiera funcionado igual sin
que existiera herencia entre Perro y Gato.

Fuente del ejemplo: [Link]


Polimorfismo
Junto con la herencia, es otra propiedad muy importante en POO. Los métodos son polimorfos. El
polimorfismo quiere decir que el método que vaya a implementar / ejecutar a la hora de ser
llamado en tiempo de ejecución va a establecer el comportamiento del objeto.
¿Por qué? Porque sabe a qué tipo de objeto está invocándole ese comportamiento, es más
dinámico que otros lenguajes donde tengo que ver qué tipo de variable/objeto es como para
determinar cómo se muestra. En este caso el propio objeto sabe como “mostrarse” (con su
método __str__ que lo imprimo con print).
En este caso no me tengo que detener a ver cómo va a ser implementado ese método,
directamente imprimo el objeto y va a saber si lo muestra con la velocidad o sin ella:

print(vehiculos[0]) Color: Blanco - Ruedas: 2 terminal


print(vehiculos[2]) Color: Negro - Ruedas: 4 - Velocidad: 180

Muestra información del auto (subclase)


Muestra información del vehículo (superclase)
Puedo iterar sobre la lista vehículos, donde puede observarse el polimorfismo, respondiendo a la
implementación del método más cercano (en este caso __str___ que sobreescribe al método __str__ de
Vehiculo.
Listado de vehículos: terminal
# Muestro todos los Vehículos (Vehículo o Auto) Color: Blanco - Ruedas: 2
print("Listado de vehículos: ") Color: Rojo - Ruedas: 4 - Velocidad: 140
for Vehiculo in vehiculos: Color: Negro - Ruedas: 4 - Velocidad: 180
print(Vehiculo) Color: Azul - Ruedas: 4 - Velocidad: 200
Clases abstractas
Un concepto importante en programación orientada a objetos es el de las clases abstractas. Son
clases en las que se pueden definir tanto métodos como propiedades, pero que no pueden ser
instanciadas directamente. Solamente se pueden usar para construir subclases (como si fueran
moldes). Permitiendo así tener una única implementación de los métodos compartidos, evitando
la duplicación de código.
Propiedades de las clases abstractas

• No pueden ser instanciadas, simplemente proporcionan una interfaz para las subclases
derivadas evitando así la duplicación de código.
• No es obligatorio 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 deben 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.
Clases abstractas
Creación de clases abstractas en Python

Para poder crear clases abstractas en Python es necesario importar la clase ABC y el decorador
abstractmethod del módulo abc (Abstract Base Classes). Un módulo que se encuentra en la
librería estándar del lenguaje, por lo que no es necesario instalarlo. Así para definir una clase
abstracta solamente se tiene que crear una clase heredada de ABC con un método abstracto.

from abc import ABC, abstractmethod abstractmethod es un


paquete con un conjunto
class Animal(ABC): de clases / métodos /
@abstractmethod funciones que podemos
def mover(self): reutilizar
pass
Animal()

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.

TypeError: Can't instantiate abstract class Animal with abstract method mover
Clases abstractas
Métodos en las subclases

Las subclases tienen que implementar todos los métodos abstractos, en el caso de que falte
alguno de ellos Python no permitirá instanciar tampoco la clase hija:

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.

Ver carpeta clases-abstractas-animales


Clases abstractas
Programa completo
[Link] [Link] [Link]

from abc import ABC, abstractmethod from animal import Animal from animal import Animal

class Animal(ABC): class Gato(Animal): class Perro(Animal):


@abstractmethod
def mover(self): def mover(self): def mover(self):
pass print("El gato se mueve.") print("El perro se está
moviendo.")
@abstractmethod def emitir_sonido(self):
def emitir_sonido(self): super().emitir_sonido() def emitir_sonido(self):
print("Animal emite sonido: print("Miau!") super().emitir_sonido()
", end="")
print("Guau, Guau!")
[Link]
from gato import Gato
from perro import Perro p1= Perro()
[Link]()
def __main__(): p1.emitir_sonido()
g1= Gato()
[Link]() if __name__ == "__main__":
g1.emitir_sonido() __main__()
sigue…
Herencia múltiple
La herencia múltiple es la capacidad de una subclase de heredar de múltiples superclases.
Esto conlleva un problema, y es que si varias superclases tienen los mismos atributos o métodos, la
subclase sólo podrá heredar de una de ellas.
En estos casos Python dará prioridad a las clases más a la izquierda en el momento de la
declaración de la subclase:
from a import A [Link] class A: [Link] from a import A [Link]
from b import B from b import B
from c import C def a(self):
print("Este método class C(B,A):
def main(): lo heredo de A")
c = C() def __init__(self):
c.a() def b(self): print("Soy de la clase C")
c.b() print("Este método
c.c() también lo heredo de A") def c(self):
print("Este método es de C")
if __name__ == "__main__": class B: [Link]
main()
def b(self):
print("Este método Ver carpeta herencia-multiple
lo heredo de B")
Herencia múltiple
Explicación del ejemplo:
[Link] es el módulo principal donde creamos la instancia del objeto C. El objeto C heredará los
métodos de las clases A y B.
La clase A tiene dos métodos llamados a y b, mientras que la clase B tiene un método llamado b.
El programa principal desde main crea el objeto C y llama al método a (de la clase A), luego al
método b, pero como las clases A y B comparten un método llamado b se llama al método de la
clase que está más a la izquierda de la subclase C (el método B), por eso en la terminal vemos “Este
método lo heredo de B” y no vemos “Este método también lo heredo de A”
from a import A [Link] from a import A [Link] class A: [Link]
from b import B from b import B
from c import C def a(self):
class C(B,A): print("Este método
def main(): lo heredo de A")
c = C() def __init__(self):
c.a() print("Soy de la clase C") def b(self):
c.b() print("Este método
c.c() def c(self): también lo heredo de A")
print("Este método es de C")
if __name__ == "__main__": class B: [Link]
main() Soy de la clase C terminal
Este método lo heredo de A def b(self):
Este método lo heredo de B print("Este método
Este método es de C lo heredo de B")
Herencia múltiple
Explicación del ejemplo:
SI fuese al revés, es decir si en vez de C(B,A) tuviésemos C(A,B) la terminal nos mostraría “Este
método también lo heredo de A” y no “Este método lo heredo de B”.

from a import A [Link] from a import A [Link]


from b import B from b import B

class C(B,A): class C(A,B):

def __init__(self): def __init__(self):


print("Soy de la clase C") print("Soy de la clase C")

def c(self): def c(self):


print("Este método es de C") print("Este método es de C")

terminal terminal


Soy de la clase C Soy de la clase C
Este método lo heredo de A Este método lo heredo de A
Este método lo heredo de B Este método también lo heredo de A
Este método es de C Este método es de C
Diagrama de clases
Es un tipo de diagrama de estructura estática que describe la estructura de un sistema mostrando las
clases del sistema, sus atributos, operaciones (o métodos), y las relaciones entre los objetos.
Para especificar la visibilidad de un miembro de la clase (es decir, cualquier atributo o método), se
coloca uno de los siguientes signos delante de ese miembro:
+ Público
- Privado
# Protegido

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.
o Los valores de los atributos son los mismos en todas las instancias
o La invocación de métodos no afecta al estado de las instancias
• Los miembros instancias tienen como ámbito una instancia específica.
o Los valores de los atributos pueden variar entre instancias
o La invocación de métodos puede afectar al estado de las instancias(es decir, cambiar el valor de
sus atributos).
Para indicar que un miembro posee un ámbito de clasificador, hay que subrayar su nombre. De lo
contrario, se asume por defecto que tendrá ámbito de instancia.

Para ampliar (agregación y composición): [Link]

También podría gustarte