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:
  • Implementar encapsulación usando propiedades, getters y setters en Python
  • Aplicar convenciones de nomenclatura para controlar el acceso a atributos
  • Crear y utilizar clases abstractas con el módulo abc
  • Diseñar interfaces robustas y mantenibles para aplicaciones profesionales

📚 Fundamentos de Encapsulación en Python

La encapsulación es el principio de ocultar los detalles internos de implementación y exponer solo las interfaces necesarias. En Python, aunque no existen modificadores de acceso explícitos como en otros lenguajes, se utilizan convenciones de nomenclatura. Los atributos que comienzan con un guión bajo (_) se consideran protegidos, y los que comienzan con doble guión bajo (__) se consideran privados. Python implementa 'name mangling' para los atributos privados, modificando internamente su nombre para dificultar el acceso directo. Esta aproximación permite flexibilidad manteniendo buenas prácticas de diseño.

Ejemplo básico de encapsulación con atributos públicos, protegidos y privados Copiar
class CuentaBancaria:
    def __init__(self, titular, saldo_inicial):
        self.titular = titular  # Público
        self._numero_cuenta = '12345'  # Protegido
        self.__saldo = saldo_inicial  # Privado
    
    def mostrar_info(self):
        return f'Titular: {self.titular}, Saldo: {self.__saldo}'

# Uso de la clase
cuenta = CuentaBancaria('Juan Pérez', 1000)
print(cuenta.titular)  # Acceso público
print(cuenta._numero_cuenta)  # Acceso protegido (no recomendado)
# print(cuenta.__saldo)  # Error: no existe
print(cuenta.mostrar_info())
Juan Pérez
12345
Titular: Juan Pérez, Saldo: 1000
Explicación: El atributo titular es público y accesible directamente. _numero_cuenta es protegido (por convención), y __saldo es privado, solo accesible desde métodos internos de la clase.

🎯 Propiedades con @property: Getters y Setters

El decorador @property permite crear atributos computados y controlar el acceso de lectura y escritura a los datos. Los getters se definen con @property, los setters con @atributo.setter, y los deleters con @atributo.deleter. Este mecanismo proporciona una interfaz limpia para validar datos, realizar cálculos automáticos y mantener la integridad de los objetos. Las propiedades permiten cambiar la implementación interna sin afectar el código cliente, siguiendo el principio de encapsulación.

Implementación de propiedades con validación de datos Copiar
class Temperatura:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, valor):
        if valor < -273.15:
            raise ValueError('Temperatura no puede ser menor al cero absoluto')
        self._celsius = valor
    
    @property
    def fahrenheit(self):
        return (self._celsius * 9/5) + 32
    
    @fahrenheit.setter
    def fahrenheit(self, valor):
        self.celsius = (valor - 32) * 5/9

# Uso de las propiedades
temp = Temperatura(25)
print(f'Celsius: {temp.celsius}')
print(f'Fahrenheit: {temp.fahrenheit}')
temp.fahrenheit = 100
print(f'Nuevo celsius: {temp.celsius}')
Celsius: 25
Fahrheit: 77.0
Nuevo celsius: 37.77777777777778
Explicación: Las propiedades permiten acceso controlado a los datos. celsius valida que no sea menor al cero absoluto, mientras fahrenheit se calcula automáticamente y puede establecer el valor en celsius.
Propiedades de solo lectura y validación avanzada Copiar
class Empleado:
    def __init__(self, nombre, salario):
        self._nombre = nombre
        self._salario = salario
        self._antiguedad = 0
    
    @property
    def nombre(self):
        return self._nombre
    
    @property
    def salario(self):
        return self._salario
    
    @salario.setter
    def salario(self, nuevo_salario):
        if nuevo_salario < 0:
            raise ValueError('El salario no puede ser negativo')
        if nuevo_salario < self._salario * 0.8:
            raise ValueError('Reducción de salario muy grande')
        self._salario = nuevo_salario
    
    @property
    def salario_anual(self):
        return self._salario * 12
    
    def aumentar_antiguedad(self):
        self._antiguedad += 1

# Demostración
emp = Empleado('Ana García', 3000)
print(f'Salario mensual: {emp.salario}')
print(f'Salario anual: {emp.salario_anual}')
emp.salario = 3500
print(f'Nuevo salario anual: {emp.salario_anual}')
Salario mensual: 3000
Salario anual: 36000
Nuevo salario anual: 42000
Explicación: La propiedad nombre es de solo lectura, salario tiene validación para evitar valores negativos o reducciones excesivas, y salario_anual se calcula automáticamente.

💡 Clases Abstractas y el Módulo ABC

Las clases abstractas definen una interfaz común que debe ser implementada por las subclases. Python utiliza el módulo abc (Abstract Base Classes) para crear clases abstractas mediante el decorador @abstractmethod. Una clase abstracta no puede ser instanciada directamente y requiere que las subclases implementen todos los métodos abstractos. Esto garantiza que las clases derivadas cumplan con un contrato específico, facilitando el polimorfismo y el diseño de APIs consistentes. Las clases abstractas pueden combinar métodos abstractos con implementaciones concretas.

Implementación de clase abstracta para formas geométricas Copiar
from abc import ABC, abstractmethod
import math

class Forma(ABC):
    def __init__(self, nombre):
        self._nombre = nombre
    
    @property
    def nombre(self):
        return self._nombre
    
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimetro(self):
        pass
    
    def describir(self):
        return f'{self.nombre}: Área={self.area():.2f}, Perímetro={self.perimetro():.2f}'

class Circulo(Forma):
    def __init__(self, radio):
        super().__init__('Círculo')
        self._radio = radio
    
    def area(self):
        return math.pi * self._radio ** 2
    
    def perimetro(self):
        return 2 * math.pi * self._radio

class Rectangulo(Forma):
    def __init__(self, ancho, alto):
        super().__init__('Rectángulo')
        self._ancho = ancho
        self._alto = alto
    
    def area(self):
        return self._ancho * self._alto
    
    def perimetro(self):
        return 2 * (self._ancho + self._alto)

# Uso de las clases
circulo = Circulo(5)
rectangulo = Rectangulo(4, 6)
print(circulo.describir())
print(rectangulo.describir())
Círculo: Área=78.54, Perímetro=31.42
Rectángulo: Área=24.00, Perímetro=20.00
Explicación: La clase abstracta Forma define la interfaz común con métodos abstractos area() y perimetro(). Las subclases deben implementar estos métodos para poder ser instanciadas.

🔧 Casos de Uso Avanzados: Sistema de Gestión

En aplicaciones profesionales, la encapsulación y abstracción se combinan para crear sistemas robustos y escalables. Un ejemplo común es un sistema de gestión donde diferentes tipos de entidades comparten comportamientos comunes pero tienen implementaciones específicas. La combinación de clases abstractas, propiedades validadas y encapsulación apropiada permite crear código mantenible y extensible. Este enfoque facilita la incorporación de nuevas funcionalidades sin modificar el código existente, siguiendo principios SOLID.

Sistema de gestión de vehículos con abstracción y encapsulación Copiar
from abc import ABC, abstractmethod

class Vehiculo(ABC):
    def __init__(self, marca, modelo, año):
        self._marca = marca
        self._modelo = modelo
        self._año = año
        self._kilometraje = 0
        self._activo = True
    
    @property
    def info_basica(self):
        return f'{self._marca} {self._modelo} ({self._año})'
    
    @property
    def kilometraje(self):
        return self._kilometraje
    
    @kilometraje.setter
    def kilometraje(self, km):
        if km < self._kilometraje:
            raise ValueError('El kilometraje no puede disminuir')
        self._kilometraje = km
    
    @abstractmethod
    def costo_mantenimiento(self):
        pass
    
    @abstractmethod
    def tipo_combustible(self):
        pass

class Auto(Vehiculo):
    def __init__(self, marca, modelo, año, puertas):
        super().__init__(marca, modelo, año)
        self._puertas = puertas
    
    def costo_mantenimiento(self):
        return 500 + (self.kilometraje * 0.1)
    
    def tipo_combustible(self):
        return 'Gasolina'

class Camion(Vehiculo):
    def __init__(self, marca, modelo, año, capacidad_carga):
        super().__init__(marca, modelo, año)
        self._capacidad_carga = capacidad_carga
    
    def costo_mantenimiento(self):
        return 1200 + (self.kilometraje * 0.2)
    
    def tipo_combustible(self):
        return 'Diésel'

# Demostración del sistema
vehiculos = [
    Auto('Toyota', 'Corolla', 2020, 4),
    Camion('Volvo', 'FH16', 2019, 25000)
]

for vehiculo in vehiculos:
    vehiculo.kilometraje = 15000
    print(f'{vehiculo.info_basica}')
    print(f'Combustible: {vehiculo.tipo_combustible()}')
    print(f'Costo mantenimiento: ${vehiculo.costo_mantenimiento():.2f}')
    print('---')
Toyota Corolla (2020)
Combustible: Gasolina
Costo mantenimiento: $2000.00
---
Volvo FH16 (2019)
Combustible: Diésel
Costo mantenimiento: $4200.00
---
Explicación: El sistema demuestra polimorfismo mediante la clase abstracta Vehiculo. Cada tipo de vehículo implementa sus métodos específicos mientras mantiene una interfaz común para el procesamiento.

Ejercicios Prácticos

Sistema de Cuentas Bancarias Encapsulado
INTERMEDIO

Descripción:

Crear una clase CuentaBancaria que implemente encapsulación completa con propiedades para saldo, número de cuenta y titular. Incluir validaciones para transferencias y retiros, y métodos para consultar historial de transacciones. El saldo debe ser privado y solo modificable a través de métodos controlados.

Pista: Utiliza @property para saldo, lista privada para transacciones y validaciones en setters
Jerarquía de Empleados con Clases Abstractas
AVANZADO

Descripción:

Diseñar un sistema de empleados con clase abstracta Empleado que defina métodos para calcular salario y beneficios. Crear subclases EmpleadoTiempoCompleto, EmpleadoMedioTiempo y Contratista, cada una con sus propias reglas de cálculo. Implementar propiedades validadas para datos personales.

Pista: Usa ABC para la clase base, @abstractmethod para cálculos y @property con validaciones
Sistema de Inventario con Abstracción
AVANZADO

Descripción:

Crear un sistema de inventario donde Producto sea una clase abstracta con métodos para calcular precio final, obtener información y validar stock. Implementar ProductoFisico y ProductoDigital con comportamientos específicos. Incluir propiedades para precio, descuentos y disponibilidad.

Pista: Combina herencia, propiedades calculadas y validación de datos en tiempo real
Validador de Datos con Propiedades
BÁSICO

Descripción:

Implementar una clase Persona que utilice propiedades para validar email, teléfono, edad y nombre. Cada propiedad debe tener validaciones específicas y proporcionar mensajes de error descriptivos. Incluir propiedades de solo lectura para datos calculados como edad en días.

Pista: Usa expresiones regulares para validar email y teléfono, rangos para edad

Resumen y Próximos Pasos

La encapsulación y abstracción son herramientas poderosas que permiten crear código Python más robusto y mantenible. Hemos aprendido a controlar el acceso a los datos mediante propiedades, implementar validaciones efectivas con getters y setters, y diseñar interfaces consistentes usando clases abstractas. Estos conceptos son fundamentales para el desarrollo de aplicaciones profesionales y sistemas escalables. En el próximo tema exploraremos patrones de diseño que aprovechan estos principios para resolver problemas comunes de arquitectura de software, incluyendo Factory, Observer y Strategy patterns.

🏷️ Etiquetas:
python programación encapsulación abstracción POO