Lección Python

Contenido de la lección

Introducción

Contenido de la lección.

Objetivos de aprendizaje
  • Objetivo 1
  • Objetivo 2
  • Objetivo 3

Objetivos de Aprendizaje

Al finalizar este tema, serás capaz de:
  • Comprender los conceptos de herencia y polimorfismo en Python
  • Implementar clases padre e hija con relaciones jerárquicas
  • Utilizar la función super() para acceder a métodos de la clase padre
  • Aplicar polimorfismo mediante sobrescritura de métodos
  • Desarrollar sistemas orientados a objetos escalables y mantenibles

📚 Conceptos Fundamentales de Herencia

La herencia es un mecanismo que permite crear una nueva clase basada en una clase existente. La clase base se denomina clase padre o superclase, mientras que la nueva clase se llama clase hija o subclase. La clase hija hereda todos los atributos y métodos de la clase padre, pudiendo además agregar nuevos atributos y métodos o modificar los existentes. En Python, la herencia se especifica colocando el nombre de la clase padre entre paréntesis después del nombre de la clase hija. Este concepto fundamental permite la reutilización de código y establece relaciones is-a entre objetos, donde la clase hija es una especialización de la clase padre.

Definición básica de herencia con clase padre Animal y clase hija Perro Copiar
class Animal:
    def __init__(self, nombre, especie):
        self.nombre = nombre
        self.especie = especie
    
    def hacer_sonido(self):
        return 'El animal hace un sonido'
    
    def dormir(self):
        return f'{self.nombre} está durmiendo'

class Perro(Animal):
    def __init__(self, nombre, raza):
        super().__init__(nombre, 'Canino')
        self.raza = raza
    
    def hacer_sonido(self):
        return 'Guau guau'

# Crear instancias
mi_perro = Perro('Max', 'Labrador')
print(f'Nombre: {mi_perro.nombre}')
print(f'Especie: {mi_perro.especie}')
print(f'Raza: {mi_perro.raza}')
print(mi_perro.hacer_sonido())
print(mi_perro.dormir())
Nombre: Max
Especie: Canino
Raza: Labrador
Guau guau
Max está durmiendo
Explicación: La clase Perro hereda de Animal, manteniendo atributos como nombre y métodos como dormir(), pero redefine hacer_sonido() y agrega el atributo raza específico.

🎯 La Función super() y su Importancia

La función super() es fundamental para acceder a métodos y atributos de la clase padre desde la clase hija. Permite llamar al constructor de la clase padre y reutilizar su funcionalidad sin duplicar código. super() retorna un objeto proxy que delega llamadas de método a la clase padre, facilitando la extensión de funcionalidad existente. Es especialmente útil en constructores donde necesitamos inicializar tanto los atributos de la clase padre como los específicos de la clase hija. El uso de super() también hace el código más mantenible, ya que cambios en la clase padre se propagan automáticamente a las clases hijas sin necesidad de modificaciones adicionales.

Uso avanzado de super() para extender funcionalidad del método padre Copiar
class Vehiculo:
    def __init__(self, marca, modelo, año):
        self.marca = marca
        self.modelo = modelo
        self.año = año
        self.encendido = False
    
    def encender(self):
        self.encendido = True
        return f'{self.marca} {self.modelo} encendido'
    
    def info(self):
        return f'{self.marca} {self.modelo} ({self.año})'

class Auto(Vehiculo):
    def __init__(self, marca, modelo, año, puertas):
        super().__init__(marca, modelo, año)
        self.puertas = puertas
    
    def encender(self):
        resultado = super().encender()
        return f'{resultado} - Revisando cinturones de seguridad'
    
    def info(self):
        info_base = super().info()
        return f'{info_base} - {self.puertas} puertas'

# Uso del ejemplo
mi_auto = Auto('Toyota', 'Corolla', 2023, 4)
print(mi_auto.info())
print(mi_auto.encender())
print(f'Estado: {"Encendido" if mi_auto.encendido else "Apagado"}')
Toyota Corolla (2023) - 4 puertas
Toyota Corolla encendido - Revisando cinturones de seguridad
Estado: Encendido
Explicación: super() permite extender la funcionalidad de métodos padre, agregando comportamiento específico de la clase hija mientras mantiene la funcionalidad original.

💡 Polimorfismo y Métodos Virtuales

El polimorfismo permite que objetos de diferentes clases respondan de manera distinta al mismo método, proporcionando flexibilidad en el diseño de software. En Python, el polimorfismo se logra principalmente através de la sobrescritura de métodos (method overriding), donde las clases hijas redefinen métodos heredados para proporcionar implementaciones específicas. Los métodos virtuales son aquellos diseñados para ser sobrescritos en las clases derivadas. Python no tiene palabras clave específicas como virtual, pero todos los métodos pueden ser sobrescritos. Esta característica permite escribir código genérico que funciona con múltiples tipos de objetos, siguiendo el principio de sustitución de Liskov.

Implementación de polimorfismo con diferentes formas geométricas Copiar
import math

class Forma:
    def area(self):
        raise NotImplementedError('Subclase debe implementar area()')
    
    def perimetro(self):
        raise NotImplementedError('Subclase debe implementar perimetro()')
    
    def describir(self):
        return f'Área: {self.area():.2f}, Perímetro: {self.perimetro():.2f}'

class Rectangulo(Forma):
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto
    
    def area(self):
        return self.ancho * self.alto
    
    def perimetro(self):
        return 2 * (self.ancho + self.alto)

class Circulo(Forma):
    def __init__(self, radio):
        self.radio = radio
    
    def area(self):
        return math.pi * self.radio ** 2
    
    def perimetro(self):
        return 2 * math.pi * self.radio

# Demostración de polimorfismo
formas = [Rectangulo(5, 3), Circulo(4), Rectangulo(2, 8)]

for i, forma in enumerate(formas, 1):
    print(f'Forma {i}: {forma.__class__.__name__}')
    print(f'  {forma.describir()}')
Forma 1: Rectangulo
  Área: 15.00, Perímetro: 16.00
Forma 2: Circulo
  Área: 50.27, Perímetro: 25.13
Forma 3: Rectangulo
  Área: 16.00, Perímetro: 20.00
Explicación: Cada clase implementa los métodos area() y perimetro() de forma específica, pero todas pueden ser tratadas como objetos Forma, demostrando polimorfismo en acción.

🔧 Herencia Múltiple y Method Resolution Order (MRO)

Python soporta herencia múltiple, permitiendo que una clase herede de múltiples clases padre. Esto proporciona gran flexibilidad pero puede crear ambigüedad sobre qué método se ejecuta cuando hay nombres duplicados. Python resuelve esto usando el Method Resolution Order (MRO), que determina el orden en que se buscan métodos en la jerarquía de clases. El MRO sigue el algoritmo C3 linearization, garantizando un orden consistente y predecible. Es crucial entender el MRO para evitar comportamientos inesperados y diseñar jerarquías de clases efectivas. El método __mro__ y la función help() pueden mostrar el orden de resolución de una clase.

Ejemplo de herencia múltiple con MRO Copiar
class A:
    def metodo(self):
        return 'Método de A'
    
    def saludar(self):
        return 'Hola desde A'

class B(A):
    def metodo(self):
        return 'Método de B'
    
    def despedir(self):
        return 'Adiós desde B'

class C(A):
    def metodo(self):
        return 'Método de C'
    
    def presentar(self):
        return 'Me presento desde C'

class D(B, C):
    def metodo_combinado(self):
        return f'Combinando: {super().metodo()}'

# Análisis del MRO
objeto = D()
print('Method Resolution Order:')
for i, clase in enumerate(D.__mro__):
    print(f'  {i+1}. {clase.__name__}')

print(f'\nMétodo llamado: {objeto.metodo()}')
print(f'Método combinado: {objeto.metodo_combinado()}')
print(f'Heredado de A: {objeto.saludar()}')
print(f'Heredado de B: {objeto.despedir()}')
print(f'Heredado de C: {objeto.presentar()}')
Method Resolution Order:
  1. D
  2. B
  3. C
  4. A
  5. object

Método llamado: Método de B
Método combinado: Combinando: Método de B
Heredado de A: Hola desde A
Heredado de B: Adiós desde B
Heredado de C: Me presento desde C
Explicación: El MRO determina que B tiene prioridad sobre C, por lo que objeto.metodo() retorna 'Método de B'. Todos los métodos únicos de cada clase están disponibles.

⚡ Casos de Uso Avanzados: Sistema de Empleados

Un ejemplo práctico común es un sistema de gestión de empleados que demuestra herencia, polimorfismo y el uso de super() en un contexto profesional. Este sistema incluye una clase base Empleado con atributos comunes y métodos generales, y clases derivadas como Desarrollador, Gerente y Diseñador, cada una con comportamientos específicos. El polimorfismo permite calcular salarios de forma diferente para cada tipo de empleado, mientras que la herencia evita duplicación de código común. Este patrón es fundamental en sistemas empresariales reales donde diferentes tipos de entidades comparten características base pero tienen comportamientos especializados.

Sistema completo de gestión de empleados con herencia y polimorfismo Copiar
class Empleado:
    def __init__(self, nombre, id_empleado, salario_base):
        self.nombre = nombre
        self.id_empleado = id_empleado
        self.salario_base = salario_base
        self.horas_trabajadas = 0
    
    def calcular_salario(self):
        return self.salario_base
    
    def registrar_horas(self, horas):
        self.horas_trabajadas += horas
        return f'{self.nombre} trabajó {horas} horas adicionales'
    
    def info_empleado(self):
        return f'{self.nombre} (ID: {self.id_empleado}) - Salario: ${self.calcular_salario():,.2f}'

class Desarrollador(Empleado):
    def __init__(self, nombre, id_empleado, salario_base, lenguajes):
        super().__init__(nombre, id_empleado, salario_base)
        self.lenguajes = lenguajes
        self.bonus_horas = 25
    
    def calcular_salario(self):
        salario_base = super().calcular_salario()
        bonus_horas_extra = self.horas_trabajadas * self.bonus_horas
        return salario_base + bonus_horas_extra
    
    def info_empleado(self):
        info_base = super().info_empleado()
        return f'{info_base} - Lenguajes: {", ".join(self.lenguajes)}'

class Gerente(Empleado):
    def __init__(self, nombre, id_empleado, salario_base, equipo_size):
        super().__init__(nombre, id_empleado, salario_base)
        self.equipo_size = equipo_size
        self.bonus_gerencial = 0.15
    
    def calcular_salario(self):
        salario_base = super().calcular_salario()
        bonus = salario_base * self.bonus_gerencial * (self.equipo_size / 10)
        return salario_base + bonus
    
    def info_empleado(self):
        info_base = super().info_empleado()
        return f'{info_base} - Equipo: {self.equipo_size} personas'

# Demostración del sistema
empleados = [
    Desarrollador('Ana García', 'DEV001', 50000, ['Python', 'Java', 'JavaScript']),
    Gerente('Carlos López', 'MNG001', 70000, 8),
    Desarrollador('María Rodríguez', 'DEV002', 48000, ['C++', 'Python'])
]

# Registrar horas extra
empleados[0].registrar_horas(10)
empleados[2].registrar_horas(15)

print('=== INFORMACIÓN DE EMPLEADOS ===')
for empleado in empleados:
    print(empleado.info_empleado())

print(f'\nNómina total: ${sum(emp.calcular_salario() for emp in empleados):,.2f}')
=== INFORMACIÓN DE EMPLEADOS ===
Ana García (ID: DEV001) - Salario: $50,250.00 - Lenguajes: Python, Java, JavaScript
Carlos López (ID: MNG001) - Salario: $78,400.00 - Equipo: 8 personas
María Rodríguez (ID: DEV002) - Salario: $48,375.00 - Lenguajes: C++, Python

Nómina total: $177,025.00
Explicación: El sistema demuestra polimorfismo con calcular_salario() implementado diferente en cada clase, y herencia con super() para extender funcionalidad base manteniendo código reutilizable.

Ejercicios Prácticos

Sistema de Vehículos con Herencia
BÁSICO

Descripción:

Crear una jerarquía de clases con Vehiculo como clase base, y Auto, Motocicleta y Camion como clases derivadas. Cada vehículo debe tener métodos para acelerar, frenar y mostrar información. Implementar polimorfismo en el método de consumo de combustible.

Pista: Usa super() en los constructores y sobrescribe métodos específicos en cada clase derivada.
Jerarquía de Figuras Geométricas 3D
INTERMEDIO

Descripción:

Extender el ejemplo de formas geométricas para incluir figuras 3D como Cubo, Esfera y Cilindro. Implementar métodos para calcular volumen y área superficial. Crear un método que compare objetos por volumen.

Pista: Considera crear una clase base Forma3D que herede de Forma y agregue el método volumen().
Sistema de Inventario con Herencia Múltiple
AVANZADO

Descripción:

Diseñar un sistema de inventario donde los productos puedan ser Vendible, Almacenable y Rastreable. Usar herencia múltiple para crear clases como ProductoElectronico que combine estas características. Implementar métodos para gestión de stock y ventas.

Pista: Cuidado con el MRO. Usa super() apropiadamente en herencia múltiple y considera el patrón de composición como alternativa.
Framework de Juego con Polimorfismo
AVANZADO

Descripción:

Crear un mini-framework de juego con una clase base Personaje y clases derivadas Guerrero, Mago y Arquero. Implementar sistema de combate polimórfico donde cada personaje tenga diferentes habilidades y formas de atacar y defenderse.

Pista: Usa métodos abstractos para forzar implementación en clases derivadas y considera usar propiedades para atributos calculados.

Resumen y Próximos Pasos

La herencia y el polimorfismo son conceptos fundamentales que permiten crear arquitecturas de software robustas y escalables. La herencia facilita la reutilización de código y establece relaciones lógicas entre clases, mientras que el polimorfismo proporciona flexibilidad permitiendo que diferentes objetos respondan de manera específica a la misma interfaz. El dominio de super() es crucial para extender funcionalidad de manera eficiente, y la comprensión del MRO es esencial para manejar herencia múltiple correctamente. Estos conceptos preparan el terreno para patrones de diseño más avanzados y arquitecturas de software complejas que se estudiarán en módulos posteriores.

🏷️ Etiquetas:
python programación herencia polimorfismo POO