Ing. Bernardo Prado · DSM3 · UTH 2026-2
Evaluación alternativa de la Unidad 2. Cada ejercicio está resuelto y explicado línea por línea, cubriendo los 4 pilares de la POO: Abstracción, Encapsulamiento, Herencia y Polimorfismo. Los diagramas UML y de casos de uso muestran el diseño completo; el bloque de código incluye la solución comentada y la salida esperada para que verifiques tu comprensión de cada concepto y repases antes del examen.
ABC), Encapsulamiento (@property), Herencia (super(), MRO) y Polimorfismo.| # | Tema | Escenario | Módulo | Puntos |
|---|---|---|---|---|
| 1 | Encapsulamiento + @property | Clínica de Salud | U2 | 15 pts |
| 2 | Herencia 3 niveles + super() | Flota de Vehículos | U2 | 15 pts |
| 3 | Clase Abstracta + Polimorfismo | Orquesta de Instrumentos | U2 | 15 pts |
| 4 | Composición | Sistema de Cafetería | U2 | 15 pts |
| 5 | @classmethod + @staticmethod | Conversor de Temperaturas | U2 | 10 pts |
| 6 | Herencia múltiple / Mixin | Animales con Mixin | U2 | 15 pts |
Una clínica registra pacientes aplicando encapsulamiento completo: __nombre privado (solo lectura), _edad protegida (setter: 0 < edad < 120), __temperatura privada (setter: 30.0 ≤ temp ≤ 45.0). El método tiene_fiebre() devuelve True si temperatura ≥ 38.0 y presentarse() muestra el estado con operador ternario.
self.__nombre = nombre (privado), self._edad = edad (protegido), self.__id_paciente = id_paciente (privado), self.__temperatura = temperatura (privado). Los dobles guiones bajos impiden acceso directo desde fuera.@property y retornar self.__nombre. Al no haber setter, intentar p.nombre = "X" lanza AttributeError automáticamente.return self._edad. Setter: if 0 < nueva_edad < 120: self._edad = nueva_edad — si no cumple, imprimir mensaje de error (sin lanzar excepción).return self.__temperatura. Setter: if 30.0 <= nueva_temp <= 45.0: self.__temperatura = nueva_temp — fuera del rango, imprimir mensaje de error sin modificar el valor.return self.__temperatura >= 38.0 — una sola línea. Accede al privado directamente desde dentro de la clase.estado = "⚠ Fiebre" if self.tiene_fiebre() else "✓ Normal". Luego print() con f-string usando las properties self.nombre, self.edad, self.temperatura y estado.# ── Ejercicio 1: Clínica de Salud — Encapsulamiento ─────────────── class Paciente: # Define la clase Paciente con encapsulamiento completo de atributos """Paciente con encapsulamiento completo y propiedades validadas.""" # Docstring de la clase def __init__(self, nombre, edad, id_paciente, temperatura=36.5): # Constructor: recibe datos del paciente; temperatura tiene valor por defecto self.__nombre = nombre # Atributo PRIVADO: nombre no puede modificarse desde fuera de la clase self._edad = edad # Atributo PROTEGIDO: edad accesible por subclases con validación self.__id_paciente = id_paciente # Atributo PRIVADO: ID del paciente, uso interno exclusivo self.__temperatura = temperatura # Atributo PRIVADO: temperatura corporal, solo via property # ── nombre: solo lectura ───────────────────────────── @property # Decorador que convierte el método en propiedad de solo lectura def nombre(self): # Getter del nombre; sin setter porque el nombre no debe cambiar return self.__nombre # Devuelve el nombre privado de forma controlada # ── edad: con setter validado ───────────────────────── @property # Decorador para acceder a _edad como si fuera atributo público def edad(self): # Getter de edad return self._edad # Devuelve la edad protegida @edad.setter # Decorador que define el setter de la propiedad edad def edad(self, nueva_edad): # Setter de edad: valida antes de asignar if 0 < nueva_edad < 120: # Valida que la edad esté en rango biológicamente válido self._edad = nueva_edad # Asigna la nueva edad si pasa la validación else: # Si la edad está fuera del rango permitido... print(f"Edad inválida: {nueva_edad}. Debe estar entre 1 y 119.") # ...muestra mensaje de error sin lanzar excepción # ── temperatura: con setter validado ────────────────── @property # Decorador para acceder a __temperatura como propiedad pública def temperatura(self): # Getter de temperatura corporal return self.__temperatura # Devuelve la temperatura privada de forma controlada @temperatura.setter # Decorador que define el setter con validación de rango def temperatura(self, nueva_temp): # Setter de temperatura: acepta solo valores entre 30 y 45 °C if 30.0 <= nueva_temp <= 45.0: # Valida que la temperatura esté en rango fisiológico self.__temperatura = nueva_temp # Asigna la nueva temperatura si es válida else: # Si la temperatura está fuera del rango permitido... print(f"Temperatura inválida: {nueva_temp}°C. Rango: 30–45") # ...muestra mensaje de error descriptivo def tiene_fiebre(self) -> bool: # Método que devuelve True si la temperatura indica fiebre (≥ 38.0 °C) return self.__temperatura >= 38.0 # Compara temperatura con el umbral clínico de fiebre def presentarse(self): # Método que imprime el estado completo del paciente estado = "⚠ Fiebre" if self.tiene_fiebre() else "✓ Normal" # Operador ternario según si hay fiebre print(f"Paciente: {self.nombre} | Edad: {self.edad} | " # Primera parte: datos de identidad del paciente f"Temp: {self.temperatura}°C | Estado: {estado}") # Segunda parte: temperatura y estado de salud if __name__ == "__main__": # Bloque de prueba principal p1 = Paciente("María García", 34, "PAC-001") # Crea un paciente con temperatura por defecto (36.5 °C) p1.presentarse() # Muestra el estado inicial del paciente (temperatura normal) p1.temperatura = 39.2 # setter válido p1.presentarse() # Muestra el estado tras subir la temperatura (fiebre detectada) p1.temperatura = 50.0 # setter inválido p1.edad = -5 # setter inválido p1.edad = 35 # setter válido print(f"Nueva edad: {p1.edad}") # Verifica que la edad se actualizó correctamente a 35
Paciente: María García | Edad: 34 | Temp: 36.5°C | Estado: ✓ Normal Paciente: María García | Edad: 34 | Temp: 39.2°C | Estado: ⚠ Fiebre Temperatura inválida: 50.0°C. Rango: 30–45 Edad inválida: -5. Debe estar entre 1 y 119. Nueva edad: 35
Una empresa de logística gestiona su flota con jerarquía de 3 niveles: Vehiculo → Camioneta → CamionetaRefrigerada. Cada nivel añade atributos propios y hace override de descripcion() llamando a super().descripcion(). Usa super().__init__() en cada constructor para encadenar la inicialización.
self.placa, self.marca, self.año. Es la raíz de la cadena; no llama super() (hereda directamente de object).super().__init__(placa, marca, año) para inicializar los atributos de Vehiculo. Luego agrega sus propios: self.capacidad_carga y self.num_ejes.super().__init__(placa, marca, año, capacidad_carga, num_ejes). Luego agrega self.temperatura_min y self.temperatura_max. La cadena completa corre: CamRef.__init__ → Camioneta.__init__ → Vehiculo.__init__.base = super().descripcion() captura el texto del nivel superior. Luego retorna f"{base} | Carga: {self.capacidad_carga} | Ejes: {self.num_ejes}".base = super().descripcion() ya trae placa + carga + ejes. Agrega el rango de temperatura: f"{base} | Temp: {self.temperatura_min}°C a {self.temperatura_max}°C".return self.descripcion(). Así print(obj) muestra la descripción completa automáticamente.# ── Ejercicio 2: Flota de Vehículos — Herencia 3 niveles ────────── class Vehiculo: # Clase base (nivel 1): define atributos y comportamiento general de cualquier vehículo def __init__(self, marca, modelo, anio): # Constructor base: recibe los datos esenciales del vehículo self.marca = marca # Almacena la marca del vehículo (ej: Toyota) self.modelo = modelo # Almacena el modelo del vehículo (ej: Hilux) self.anio = anio # Almacena el año de fabricación del vehículo def descripcion(self): # Método que retorna la descripción básica del vehículo return f"{self.anio} {self.marca} {self.modelo}" # Formatea: año + marca + modelo (ej: "2022 Toyota Hilux") def __str__(self): # Representación legible del vehículo base para print() return f"Vehículo: {self.descripcion()}" # Prefija "Vehículo:" a la descripción generada class Camioneta(Vehiculo): # Clase nivel 2: hereda de Vehiculo y añade datos de carga def __init__(self, marca, modelo, anio, capacidad_kg, num_ejes): # Constructor: recibe datos de Vehiculo + capacidad y ejes super().__init__(marca, modelo, anio) # Llama al constructor padre para inicializar marca, modelo y año self.capacidad_kg = capacidad_kg # Almacena la capacidad de carga en kilogramos self.num_ejes = num_ejes # Almacena el número de ejes del vehículo def descripcion(self): # Override de descripcion(): extiende la descripción base con datos de carga base = super().descripcion() # Obtiene la descripción del nivel superior (Vehiculo) return f"{base} | {self.capacidad_kg} kg | {self.num_ejes} ejes" # Agrega capacidad y número de ejes class CamionetaRefrigerada(Camioneta): # Clase nivel 3: hereda de Camioneta y añade sistema de frío def __init__(self, marca, modelo, anio, # Constructor: todos los parámetros de Camioneta más el rango de temperatura capacidad_kg, num_ejes, temp_min, temp_max): # Completa la firma del método con los parámetros restantes, iniciada en la línea anterior super().__init__(marca, modelo, anio, capacidad_kg, num_ejes) # Encadena hasta Camioneta → Vehiculo via super() self.temp_min = temp_min # Temperatura mínima de la cámara de refrigeración (ej: -18.0 °C) self.temp_max = temp_max # Temperatura máxima de la cámara de refrigeración (ej: 4.0 °C) def descripcion(self): # Override de nivel 3: agrega rango de temperatura a la descripción de Camioneta base = super().descripcion() # Obtiene descripción completa de Camioneta (ya incluye capacidad y ejes) return f"{base} | Frío: {self.temp_min}°C a {self.temp_max}°C" # Añade el rango de frío de la cámara def __str__(self): # Override de __str__: etiqueta la camioneta como refrigerada return f"[Refrig] {self.descripcion()}" # Añade prefijo "[Refrig]" para identificar el tipo especial if __name__ == "__main__": # Bloque de prueba con los 3 niveles de la jerarquía flota = [ # Lista que representa la flota con uno de cada tipo de vehículo Vehiculo("Toyota", "Hilux", 2022), # Vehículo base: sin capacidad ni refrigeración Camioneta("Ford", "F-350", 2021, 2500, 2), # Camioneta nivel 2: capacidad 2500 kg, 2 ejes CamionetaRefrigerada("Mercedes", "Sprinter", 2023, # Camioneta refrigerada nivel 3... 1800, 2, -18.0, 4.0), # ...capacidad 1800 kg, 2 ejes, rango -18°C a 4°C ] # Fin de la lista flota for v in flota: # Itera sobre cada vehículo de la flota print(v) # Llama a __str__ de cada vehículo (polimorfismo: cada clase imprime diferente)
Vehículo: 2022 Toyota Hilux Vehículo: 2021 Ford F-350 | 2500 kg | 2 ejes [Refrig] 2023 Mercedes Sprinter | 1800 kg | 2 ejes | Frío: -18.0°C a 4.0°C
Una orquesta registra instrumentos. Instrumento es clase abstracta (hereda de ABC) con dos métodos abstractos: tocar() y afinar(). El método concreto describir() se implementa en la base. Crea 3 subclases: Guitarra, Piano, Violin. El polimorfismo se demuestra iterando la lista y llamando tocar().
__init__ guarda self.nombre y self.familia. Decorar tocar() y afinar() con @abstractmethod + pass — Python prohíbe instanciar esta clase directamente y exige que cada subclase los implemente.Instrumento: retorna f-string con nombre y familia. Todas las subclases lo heredan sin reescribirlo — esto es reutilización de código.super().__init__(nombre, familia) para inicializar la base. Luego cada subclase agrega su atributo propio: Guitarra → self.num_cuerdas, Piano → self.num_teclas, Violin → self.tipo_arco.Guitarra → "rasgar cuerdas", Piano → "presionar teclas", Violin → "deslizar arco". Este es el polimorfismo: mismo método, comportamiento distinto.tocar(), cada clase define su forma de afinarse. Sin esta implementación Python lanza TypeError al instanciar.for instr in orquesta: instr.tocar() — sin importar el tipo concreto, Python llama el método correcto de cada objeto. Todos comparten la interfaz de Instrumento.# ── Ejercicio 3: Orquesta — Abstracción y Polimorfismo ──────────── from abc import ABC, abstractmethod # Importa ABC y abstractmethod del módulo abc para crear clases abstractas class Instrumento(ABC): # Clase abstracta base: no puede instanciarse directamente, es un contrato para subclases """Clase abstracta base para instrumentos musicales.""" # Docstring de la clase abstracta def __init__(self, nombre, familia, anio_fabricacion): # Constructor base: datos comunes a todos los instrumentos self.nombre = nombre # Nombre del instrumento (ej: "Gibson Les Paul") self.familia = familia # Familia musical a la que pertenece (Cuerda, Teclado, etc.) self.anio_fabricacion = anio_fabricacion # Año en que fue fabricado el instrumento @abstractmethod # Decorador: obliga a todas las subclases a implementar este método def tocar(self) -> str: # Método abstracto: cada instrumento toca de forma diferente pass # Sin implementación en la base; las subclases definen el comportamiento concreto @abstractmethod # Decorador: obliga a todas las subclases a implementar este método def afinar(self) -> str: # Método abstracto: cada instrumento se afina de forma diferente pass # Sin implementación en la base; cada subclase define cómo se afina def describir(self): # método concreto en la base print(f"{self.nombre} | {self.familia} | Año: {self.anio_fabricacion}") # Imprime nombre, familia y año del instrumento class Guitarra(Instrumento): # Subclase concreta: implementa los métodos abstractos para una guitarra def __init__(self, nombre, anio_fabricacion, cuerdas=6): # Constructor: nombre y año heredados + número de cuerdas super().__init__(nombre, "Cuerda", anio_fabricacion) # Llama al constructor de Instrumento con familia "Cuerda" self.cuerdas = cuerdas # Almacena el número de cuerdas (por defecto 6) def tocar(self) -> str: # Implementación concreta de tocar() para la guitarra return f"🎸 {self.nombre}: rasgueo de {self.cuerdas} cuerdas" # Describe la acción específica de tocar guitarra def afinar(self) -> str: # Implementación concreta de afinar() para la guitarra return "Guitarra: afinada E estándar (EADGBE)" # Retorna la afinación estándar de la guitarra class Piano(Instrumento): # Subclase concreta: implementa los métodos abstractos para un piano def __init__(self, nombre, anio_fabricacion, teclas=88): # Constructor: nombre y año + número de teclas (88 por defecto) super().__init__(nombre, "Teclado", anio_fabricacion) # Llama al constructor de Instrumento con familia "Teclado" self.teclas = teclas # Almacena el número de teclas del piano (por defecto 88) def tocar(self) -> str: # Implementación concreta de tocar() para el piano return f"🎹 {self.nombre}: melodía en {self.teclas} teclas" # Describe la acción de tocar el piano def afinar(self) -> str: # Implementación concreta de afinar() para el piano return "Piano: afinado por técnico certificado" # El piano requiere afinador profesional class Violin(Instrumento): # Subclase concreta: implementa los métodos abstractos para un violín def __init__(self, nombre, anio_fabricacion, tamanio="4/4"): # Constructor: nombre y año + tamaño (4/4 por defecto) super().__init__(nombre, "Cuerda-Arco", anio_fabricacion) # Llama al constructor de Instrumento con familia "Cuerda-Arco" self.tamanio = tamanio # Almacena el tamaño del violín (4/4 es el estándar adulto) def tocar(self) -> str: # Implementación concreta de tocar() para el violín return f"🎻 {self.nombre}: arco sobre cuerdas, tamaño {self.tamanio}" # Describe la técnica de arco del violín def afinar(self) -> str: # Implementación concreta de afinar() para el violín return "Violín: afinado con diapasón" # El violín se afina usando un diapasón como referencia if __name__ == "__main__": # Bloque de prueba que demuestra el polimorfismo de la orquesta orquesta = [ # Lista de instrumentos de distintos tipos (polimorfismo en acción) Guitarra("Gibson Les Paul", 2019), # Crea una guitarra con 6 cuerdas por defecto Piano("Steinway D-274", 2015), # Crea un piano con 88 teclas por defecto Violin("Stradivarius", 1710), # Crea un violín con tamaño 4/4 por defecto ] # Fin de la lista orquesta for inst in orquesta: # Itera sobre cada instrumento; el tipo real determina qué método se llama inst.describir() # Llama al método concreto de la clase base (igual para todos) print(" ▶", inst.tocar()) # Polimorfismo: cada instrumento ejecuta su propia implementación de tocar() print(" ↳", inst.afinar()) # Polimorfismo: cada instrumento ejecuta su propia implementación de afinar() print() # Línea en blanco para separar visualmente cada instrumento en la salida
Gibson Les Paul | Cuerda | Año: 2019
▶ 🎸 Gibson Les Paul: rasgueo de 6 cuerdas
↳ Guitarra: afinada E estándar (EADGBE)
Steinway D-274 | Teclado | Año: 2015
▶ 🎹 Steinway D-274: melodía en 88 teclas
↳ Piano: afinado por técnico certificado
Stradivarius | Cuerda-Arco | Año: 1710
▶ 🎻 Stradivarius: arco sobre cuerdas, tamaño 4/4
↳ Violín: afinado con diapasón
Una cafetería necesita registrar pedidos de clientes. Cada Pedido contiene varios objetos Producto, por lo que se debe aplicar composición: el pedido administra su propia lista de productos, calcula el total, aplica descuentos y limita la cantidad máxima de artículos permitidos.
nombre, precio y categoria como atributos de instancia. Cada producto existe como objeto independiente que luego será agregado a un pedido.numero_pedido, cliente, una lista vacía de productos y un acumulador de descuento. La lista vive dentro del pedido, mostrando la relación de composición.MAXIMO_PRODUCTOS. Si hay espacio, agrega el objeto recibido a la lista y devuelve True; si no, devuelve False.sum() con una comprensión para recorrer todos los productos del pedido y sumar sus precios. Este método centraliza el cálculo del subtotal.__str__.# ── Ejercicio 4: Sistema de Cafetería — Composición ─────────────── class Producto: # Define la entidad básica que representa cada artículo vendible de la cafetería def __init__(self, nombre, precio, categoria): # Construye un producto con su nombre, precio y categoría para reutilizarlo dentro de pedidos self.nombre = nombre # Guarda el nombre para identificar el producto en reportes y tickets self.precio = precio # Guarda el precio unitario porque será usado al calcular el total del pedido self.categoria = categoria # Guarda la categoría para clasificar el producto dentro del sistema def __str__(self): # Devuelve una representación legible del producto para mostrarlo en pantalla return f"{self.nombre} ({self.categoria}) - L {self.precio:.2f}" # Construye un texto claro con nombre, categoría y precio formateado class Pedido: # Define la clase contenedora que compone y administra múltiples objetos Producto MAXIMO_PRODUCTOS = 10 # Atributo de clase que fija el límite máximo de productos permitidos por pedido def __init__(self, numero_pedido, cliente): # Inicializa un pedido con sus datos principales y su composición interna self.numero_pedido = numero_pedido # Guarda el identificador del pedido para rastrearlo de forma única self.cliente = cliente # Guarda el nombre del cliente asociado al pedido actual self.productos = [] # Crea la lista interna que contendrá los objetos Producto del pedido self.descuento_aplicado = 0.0 # Inicia el descuento en cero para poder calcular un total final consistente def agregar_producto(self, producto): # Intenta agregar un objeto Producto al pedido respetando el límite permitido if len(self.productos) < Pedido.MAXIMO_PRODUCTOS: # Verifica que todavía exista espacio antes de modificar la composición self.productos.append(producto) # Inserta el producto en la lista interna para que pase a formar parte del pedido return True # Informa que la operación fue exitosa y que el producto quedó agregado return False # Informa que no se agregó porque el pedido ya alcanzó su capacidad máxima def calcular_total(self): # Suma los precios de todos los productos actualmente contenidos en el pedido return sum(producto.precio for producto in self.productos) # Recorre la composición y devuelve el subtotal acumulado def aplicar_descuento(self, porcentaje): # Calcula y registra el descuento a partir del porcentaje recibido total = self.calcular_total() # Obtiene primero el subtotal para usarlo como base del descuento self.descuento_aplicado = total * (porcentaje / 100) # Guarda cuánto dinero se rebaja para reutilizarlo en la impresión final return total - self.descuento_aplicado # Retorna el total final luego de restar el descuento calculado def __str__(self): # Resume el pedido completo en una sola cadena lista para mostrarse al usuario detalle = ", ".join(producto.nombre for producto in self.productos) or "Sin productos" # Construye el detalle de nombres o usa un texto alterno si la lista está vacía total_final = self.calcular_total() - self.descuento_aplicado # Calcula el valor final usando el subtotal menos el descuento almacenado return f"Pedido {self.numero_pedido} | Cliente: {self.cliente} | Productos: {len(self.productos)} | Total: L {total_final:.2f} | Detalle: {detalle}" # Devuelve la vista textual completa del pedido if __name__ == "__main__": # Ejecuta una prueba rápida del modelo solo cuando este archivo se corre directamente pedido = Pedido("P-1001", "Lucía") # Crea un pedido nuevo que actuará como objeto compuesto principal del ejemplo pedido.agregar_producto(Producto("Latte", 65.0, "Bebida")) # Crea y agrega un primer producto directamente al pedido pedido.agregar_producto(Producto("Croissant", 38.0, "Panadería")) # Agrega un segundo producto para demostrar la composición múltiple pedido.agregar_producto(Producto("Tarta", 52.0, "Postre")) # Agrega un tercer producto para completar el ejemplo de cálculo print(f"Total sin descuento: L {pedido.calcular_total():.2f}") # Muestra el subtotal antes de aplicar cualquier rebaja print(f"Total con descuento: L {pedido.aplicar_descuento(10):.2f}") # Aplica un 10% y muestra el total final resultante print(pedido) # Imprime el pedido completo usando el método mágico __str__
Total sin descuento: L 155.00 Total con descuento: L 139.50 Pedido P-1001 | Cliente: Lucía | Productos: 3 | Total: L 139.50 | Detalle: Latte, Croissant, Tarta
Un laboratorio desea trabajar con temperaturas en varias escalas sin repetir fórmulas por todo el sistema. La clase Temperatura debe usar @staticmethod para conversiones matemáticas puras y @classmethod como fábricas para crear instancias a partir de Fahrenheit o Kelvin.
self ni cls porque solo aplican fórmulas. Son ideales para convertir Celsius a Fahrenheit, Celsius a Kelvin y Fahrenheit a Celsius.cls, convierten el valor externo a Celsius y devuelven cls(...). Así pueden crear objetos Temperatura sin acoplarse al nombre de la clase.# ── Ejercicio 5: Conversor de Temperaturas ─────────────────────── class Temperatura: # Modela una temperatura y concentra la lógica de conversión entre escalas def __init__(self, valor, escala="C"): # Construye la instancia con un valor numérico y una escala por defecto en Celsius self.valor = float(valor) # Convierte el valor a float para trabajar siempre con números decimales consistentes self.escala = escala.upper() # Normaliza la escala a mayúsculas para evitar errores por diferencias de escritura @staticmethod # Declara un método estático porque esta fórmula no depende de un objeto ni de la clase def celsius_a_fahrenheit(c): # Convierte un valor en Celsius a Fahrenheit usando la fórmula estándar return (c * 9 / 5) + 32 # Devuelve la equivalencia matemática exacta en grados Fahrenheit @staticmethod # Declara otro método estático para encapsular una conversión pura def fahrenheit_a_celsius(f): # Convierte un valor en Fahrenheit a Celsius para reutilizarlo en fábricas y conversiones return (f - 32) * 5 / 9 # Aplica la fórmula inversa y devuelve el resultado en grados Celsius @staticmethod # Declara un tercer método estático porque solo realiza cálculo sin usar estado interno def celsius_a_kelvin(c): # Convierte un valor en Celsius a Kelvin sumando el desplazamiento absoluto return c + 273.15 # Devuelve el valor equivalente en Kelvin para otras conversiones y reportes @classmethod # Declara un método de clase porque debe construir y retornar una nueva instancia def desde_fahrenheit(cls, f): # Crea un objeto Temperatura a partir de una lectura externa en Fahrenheit return cls(cls.fahrenheit_a_celsius(f), "C") # Convierte a Celsius y usa cls para fabricar la instancia resultante @classmethod # Declara otro método de clase para centralizar la creación desde Kelvin def desde_kelvin(cls, k): # Crea un objeto Temperatura transformando Kelvin hacia la escala base Celsius return cls(k - 273.15, "C") # Resta el desplazamiento absoluto y retorna la instancia construida def convertir(self, escala_destino): # Convierte la instancia actual a otra escala y retorna un nuevo objeto Temperatura escala_destino = escala_destino.upper() # Normaliza la escala de destino para aceptar letras minúsculas o mayúsculas celsius = self.valor if self.escala == "C" else self.fahrenheit_a_celsius(self.valor) if self.escala == "F" else self.valor - 273.15 # Lleva cualquier origen a Celsius para unificar la lógica if escala_destino == "C": # Revisa si el usuario pidió el resultado en Celsius return Temperatura(celsius, "C") # Retorna una nueva instancia ya expresada en grados Celsius if escala_destino == "F": # Revisa si el usuario pidió el resultado en Fahrenheit return Temperatura(self.celsius_a_fahrenheit(celsius), "F") # Usa la fórmula estática y crea la nueva instancia convertida return Temperatura(self.celsius_a_kelvin(celsius), "K") # Si no fue C ni F, retorna la conversión equivalente en Kelvin def __str__(self): # Devuelve una representación amigable para imprimir la temperatura fácilmente return f"{self.valor:.2f} °{self.escala}" # Formatea el valor con dos decimales y añade la escala correspondiente if __name__ == "__main__": # Ejecuta una demostración simple solo cuando el archivo se usa como programa principal t1 = Temperatura(25) # Crea una temperatura base en Celsius para probar la conversión de instancia t2 = Temperatura.desde_fahrenheit(86) # Usa el método de clase para crear una instancia equivalente a 86 °F t3 = Temperatura.desde_kelvin(300) # Usa el otro método de clase para crear una instancia desde 300 K print(f"Original: {t1}") # Muestra la temperatura original para establecer el punto de partida print(f"A Fahrenheit: {t1.convertir('F')}") # Convierte la instancia actual a Fahrenheit y muestra el resultado print(f"Desde 86°F: {t2}") # Imprime la instancia creada por la fábrica desde Fahrenheit print(f"Desde 300K: {t3}") # Imprime la instancia creada por la fábrica desde Kelvin print(f"25°C en Kelvin: {Temperatura.celsius_a_kelvin(25):.2f}") # Llama a un método estático directamente desde la clase para demostrar su uso
Original: 25.00 °C A Fahrenheit: 77.00 °F Desde 86°F: 30.00 °C Desde 300K: 26.85 °C 25°C en Kelvin: 298.15
Un zoológico educativo quiere modelar animales que comparten comportamientos opcionales. Para ello se define una clase abstracta Animal y dos mixins reutilizables: VolarMixin y NadarMixin. Algunas subclases heredarán solo de Animal y otras combinarán varios comportamientos mediante herencia múltiple.
sonido() como método obligatorio. También ofrece describir() como comportamiento compartido por todas las subclases.Animal e implementa su propio sonido. Sirve para contrastar una subclase normal frente a las que usan mixins.Animal, VolarMixin y NadarMixin. Puede describirse, emitir sonido y además volar y nadar gracias a la herencia múltiple.Animal y VolarMixin, por lo que puede volar pero no nadar. Esto deja claro que cada subclase toma solo los comportamientos que necesita.Pato.__mro__ ayuda a visualizar la ruta seguida por el intérprete.# ── Ejercicio 6: Animales con Mixin ────────────────────────────── from abc import ABC, abstractmethod # Importa utilidades del módulo abc para definir una clase base abstracta real class VolarMixin: # Define un mixin pequeño que agrega la capacidad de volar a quien lo herede def volar(self): # Implementa el comportamiento de vuelo reutilizable para varias subclases return f"{self.nombre} está volando." # Devuelve un mensaje usando el nombre almacenado por la clase principal class NadarMixin: # Define un mixin independiente que aporta la capacidad de nadar def nadar(self): # Implementa el comportamiento de nado reutilizable para distintas especies return f"{self.nombre} está nadando." # Devuelve un texto descriptivo del movimiento acuático class Animal(ABC): # Declara la superclase abstracta que concentra datos comunes y contrato obligatorio def __init__(self, nombre, especie): # Inicializa los atributos básicos que todo animal del sistema necesita self.nombre = nombre # Guarda el nombre propio para identificar al animal en pantalla self.especie = especie # Guarda la especie para complementar la descripción general @abstractmethod # Obliga a cada subclase concreta a definir su sonido característico def sonido(self): # Declara el método abstracto que actúa como contrato polimórfico pass # Se deja vacío porque la implementación depende de cada animal concreto def describir(self): # Ofrece un método compartido que cualquier subclase puede reutilizar sin cambios return f"{self.nombre} ({self.especie})" # Devuelve una descripción corta del animal con nombre y especie class Perro(Animal): # Hereda solo de Animal para mostrar un caso sin herencia múltiple def sonido(self): # Implementa el contrato abstracto con el sonido típico de un perro return "Guau guau" # Retorna el ladrido que diferencia a esta subclase de las demás class Pato(Animal, VolarMixin, NadarMixin): # Combina la base abstracta con dos mixins para reutilizar comportamientos def sonido(self): # Implementa el método abstracto exigido por Animal return "Cuac cuac" # Retorna el sonido propio del pato para completar su comportamiento class Aguila(Animal, VolarMixin): # Combina Animal con VolarMixin para agregar solo el comportamiento de vuelo def sonido(self): # Implementa el contrato abstracto con el sonido de un águila return "Screech" # Retorna el sonido característico de esta ave rapaz if __name__ == "__main__": # Ejecuta una prueba demostrativa del polimorfismo y la herencia múltiple perro = Perro("Max", "Canino") # Crea un perro que solo posee los métodos de Animal y su propia implementación pato = Pato("Donald", "Anátida") # Crea un pato que combinará describir, sonido, volar y nadar aguila = Aguila("Ares", "Ave rapaz") # Crea un águila que describirá, sonará y volará gracias al mixin print(f"{perro.describir()} hace: {perro.sonido()}") # Muestra que Perro cumple el contrato abstracto sin mixins adicionales print(f"{pato.describir()} hace: {pato.sonido()}") # Muestra la descripción y el sonido del pato como subclase concreta print(f"Puede volar: {pato.volar()}") # Demuestra que el pato obtiene volar() desde VolarMixin print(f"Puede nadar: {pato.nadar()}") # Demuestra que el pato obtiene nadar() desde NadarMixin print(f"{aguila.describir()} hace: {aguila.sonido()}") # Muestra el comportamiento concreto del águila como otra subclase print(f"Puede volar: {aguila.volar()}") # Demuestra que el águila reutiliza el mixin de vuelo print(f"MRO Pato: {[clase.__name__ for clase in Pato.__mro__]}") # Imprime el Method Resolution Order para visualizar el orden de búsqueda de métodos
Max (Canino) hace: Guau guau Donald (Anátida) hace: Cuac cuac Puede volar: Donald está volando. Puede nadar: Donald está nadando. Ares (Ave rapaz) hace: Screech Puede volar: Ares está volando. MRO Pato: ['Pato', 'Animal', 'ABC', 'VolarMixin', 'NadarMixin', 'object']