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 la diferencia entre errores de sintaxis y excepciones en tiempo de ejecución
  • Implementar bloques try-except-finally para manejo robusto de errores
  • Crear y utilizar excepciones personalizadas para casos específicos de aplicación
  • Aplicar mejores prácticas en el manejo de excepciones en proyectos profesionales

📚 Conceptos Fundamentales de Excepciones

Una excepción es un evento que ocurre durante la ejecución de un programa que interrumpe el flujo normal de las instrucciones. Python tiene muchas excepciones incorporadas como ValueError, TypeError, FileNotFoundError, etc. Cuando ocurre un error, Python 'levanta' (raise) una excepción. Si no se maneja, el programa termina y muestra un mensaje de error. El mecanismo de manejo de excepciones permite al programador anticipar errores potenciales y definir cómo responder a ellos. Esto es crucial para crear aplicaciones robustas que puedan continuar funcionando incluso cuando ocurren problemas inesperados.

Ejemplo básico de excepción no manejada Copiar
# Intento de división por cero (genera ZeroDivisionError)
numero = 10
divisor = 0
resultado = numero / divisor
print(f'El resultado es: {resultado}')
ZeroDivisionError: division by zero
Explicación: Este código genera una excepción ZeroDivisionError que detiene la ejecución del programa
Ejemplo de TypeError con tipos incompatibles Copiar
# Intento de sumar número y cadena
numero = 5
texto = 'Hola'
resultado = numero + texto
print(resultado)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Explicación: Python no puede sumar un entero y una cadena, por lo que levanta una excepción TypeError

🎯 Estructura Try-Except Básica

El bloque try-except es la estructura fundamental para manejar excepciones en Python. El código que puede generar una excepción se coloca en el bloque try, mientras que el código para manejar la excepción se coloca en el bloque except. Cuando ocurre una excepción en el bloque try, Python busca un bloque except que pueda manejar ese tipo específico de excepción. Si encuentra uno, ejecuta el código del bloque except en lugar de terminar el programa. Esta estructura permite que el programa continúe ejecutándose incluso después de encontrar errores.

Manejo básico de división por cero Copiar
try:
    numero = 10
    divisor = 0
    resultado = numero / divisor
    print(f'El resultado es: {resultado}')
except ZeroDivisionError:
    print('Error: No se puede dividir por cero')
print('El programa continúa ejecutándose')
Error: No se puede dividir por cero
El programa continúa ejecutándose
Explicación: El bloque except captura la excepción ZeroDivisionError y ejecuta código alternativo
Manejo de múltiples tipos de excepciones Copiar
def operacion_segura(a, b):
    try:
        resultado = int(a) / int(b)
        return f'Resultado: {resultado}'
    except ValueError:
        return 'Error: Valores no numéricos'
    except ZeroDivisionError:
        return 'Error: División por cero'

print(operacion_segura('10', '2'))
print(operacion_segura('abc', '5'))
print(operacion_segura('8', '0'))
Resultado: 5.0
Error: Valores no numéricos
Error: División por cero
Explicación: Cada tipo de excepción se maneja con un bloque except específico

💡 Cláusula Finally y Manejo Avanzado

El bloque finally se ejecuta siempre, sin importar si ocurrió una excepción o no. Es ideal para código de limpieza como cerrar archivos o conexiones de base de datos. También podemos usar 'else' después de except, que se ejecuta solo si no ocurre ninguna excepción. La cláusula 'as' nos permite capturar el objeto excepción para acceder a información detallada del error. Además, podemos capturar múltiples excepciones en un solo bloque except usando tuplas.

Uso completo de try-except-else-finally Copiar
def leer_archivo(nombre_archivo):
    archivo = None
    try:
        archivo = open(nombre_archivo, 'r')
        contenido = archivo.read()
        print(f'Archivo leído exitosamente')
    except FileNotFoundError as e:
        print(f'Error: Archivo no encontrado - {e}')
    except PermissionError:
        print('Error: Sin permisos para leer el archivo')
    else:
        print('Operación completada sin errores')
    finally:
        if archivo:
            archivo.close()
        print('Recursos liberados')

leer_archivo('archivo_inexistente.txt')
Error: Archivo no encontrado - [Errno 2] No such file or directory: 'archivo_inexistente.txt'
Recursos liberados
Explicación: El bloque finally siempre se ejecuta para liberar recursos, sin importar si hubo errores
Captura de múltiples excepciones con información detallada Copiar
def procesar_datos(datos):
    try:
        # Simulamos operaciones que pueden fallar
        if not datos:
            raise ValueError('Los datos están vacíos')
        resultado = sum(datos) / len(datos)
        return resultado
    except (TypeError, ValueError) as e:
        print(f'Error de datos: {type(e).__name__}: {e}')
        return None
    except Exception as e:
        print(f'Error inesperado: {type(e).__name__}: {e}')
        return None

print(procesar_datos([1, 2, 3, 4]))
print(procesar_datos([]))
print(procesar_datos('no es lista'))
2.5
Error de datos: ValueError: Los datos están vacíos
None
Error de datos: TypeError: unsupported operand type(s) for +: 'int' and 'str'
None
Explicación: Un solo bloque except maneja múltiples tipos de excepciones y proporciona información detallada

🔧 Creación de Excepciones Personalizadas

Las excepciones personalizadas permiten crear tipos de errores específicos para nuestras aplicaciones. Se crean heredando de la clase Exception o de sus subclases. Esto mejora la legibilidad del código y permite un manejo más específico de errores. Las excepciones personalizadas deben tener nombres descriptivos que terminen en 'Error' o 'Exception'. Pueden incluir atributos adicionales y métodos para proporcionar más información sobre el error. Es una práctica profesional crear jerarquías de excepciones para diferentes módulos de una aplicación.

Creación básica de excepción personalizada Copiar
class EdadInvalidaError(Exception):
    '''Excepción levantada cuando la edad es inválida'''
    def __init__(self, edad, mensaje='Edad fuera del rango válido'):
        self.edad = edad
        self.mensaje = mensaje
        super().__init__(self.mensaje)

def validar_edad(edad):
    if edad < 0 or edad > 150:
        raise EdadInvalidaError(edad, f'La edad {edad} no es válida')
    return f'Edad válida: {edad} años'

try:
    print(validar_edad(25))
    print(validar_edad(200))
except EdadInvalidaError as e:
    print(f'Error personalizado: {e.mensaje}')
    print(f'Edad problemática: {e.edad}')
Edad válida: 25 años
Error personalizado: La edad 200 no es válida
Edad problemática: 200
Explicación: La excepción personalizada incluye información específica sobre el error ocurrido
Jerarquía de excepciones personalizadas Copiar
class BancoError(Exception):
    '''Excepción base para errores bancarios'''
    pass

class SaldoInsuficienteError(BancoError):
    '''Error cuando no hay suficiente saldo'''
    def __init__(self, saldo_actual, monto_solicitado):
        self.saldo_actual = saldo_actual
        self.monto_solicitado = monto_solicitado
        super().__init__(f'Saldo insuficiente: tienes ${saldo_actual}, necesitas ${monto_solicitado}')

class CuentaBloqueadaError(BancoError):
    '''Error cuando la cuenta está bloqueada'''
    pass

def retirar_dinero(saldo, monto, cuenta_activa=True):
    if not cuenta_activa:
        raise CuentaBloqueadaError('La cuenta está bloqueada')
    if monto > saldo:
        raise SaldoInsuficienteError(saldo, monto)
    return saldo - monto

try:
    nuevo_saldo = retirar_dinero(100, 150)
except SaldoInsuficienteError as e:
    print(f'Error de saldo: {e}')
except BancoError as e:
    print(f'Error bancario: {e}')
Error de saldo: Saldo insuficiente: tienes $100, necesitas $150
Explicación: Las excepciones personalizadas forman una jerarquía que permite manejo específico o general

⚡ Casos de Uso Profesionales y Mejores Prácticas

En aplicaciones profesionales, el manejo de excepciones debe ser estratégico y específico. Es importante capturar solo las excepciones que podemos manejar apropiadamente, no usar except genérico sin tipo específico, y siempre limpiar recursos en finally o usar context managers. El logging de errores es crucial para debugging en producción. También debemos considerar la propagación de excepciones: a veces es mejor dejar que la excepción se propague hacia arriba en lugar de manejarla localmente. El principio 'fail fast' sugiere detectar errores lo antes posible en lugar de permitir estados corruptos.

Manejo profesional con logging y context managers Copiar
import logging
from contextlib import contextmanager

# Configuración básica de logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class DatabaseError(Exception):
    pass

@contextmanager
def conexion_database():
    '''Simula una conexión a base de datos'''
    logger.info('Estableciendo conexión a la base de datos')
    try:
        # Simular conexión
        conexion = {'conectado': True}
        yield conexion
    except Exception as e:
        logger.error(f'Error en operación de base de datos: {e}')
        raise DatabaseError(f'Falla en base de datos: {e}')
    finally:
        logger.info('Cerrando conexión a la base de datos')

def operacion_critica():
    try:
        with conexion_database() as db:
            if not db['conectado']:
                raise DatabaseError('Conexión fallida')
            logger.info('Operación completada exitosamente')
            return 'Datos procesados'
    except DatabaseError as e:
        logger.error(f'Error crítico: {e}')
        return None

resultado = operacion_critica()
print(f'Resultado: {resultado}')
INFO:__main__:Estableciendo conexión a la base de datos
INFO:__main__:Operación completada exitosamente
INFO:__main__:Cerrando conexión a la base de datos
Resultado: Datos procesados
Explicación: Combina context managers, logging y excepciones personalizadas para un manejo robusto

Ejercicios Prácticos

Validador de Entrada de Usuario
INTERMEDIO

Descripción:

Crea una función que valide la entrada del usuario para un formulario de registro. Debe manejar diferentes tipos de errores: email inválido, contraseña débil, edad fuera de rango. Crea excepciones personalizadas para cada tipo de error y un sistema de validación completo.

Pista: Define excepciones personalizadas para cada tipo de validación y usa expresiones regulares para validar el email
Gestor de Archivos Robusto
AVANZADO

Descripción:

Implementa una clase que maneje operaciones de archivos (leer, escribir, copiar) con manejo completo de excepciones. Debe manejar archivos no encontrados, permisos insuficientes, espacio en disco, etc. Incluye logging y limpieza automática de recursos.

Pista: Usa context managers y el bloque finally para garantizar que los recursos se liberen correctamente
Calculadora Científica con Manejo de Errores
BÁSICO

Descripción:

Desarrolla una calculadora que maneje operaciones matemáticas complejas con manejo robusto de errores: división por cero, raíces de números negativos, logaritmos de números no positivos, etc. Crea excepciones específicas para cada tipo de error matemático.

Pista: Crea una jerarquía de excepciones matemáticas y valida las entradas antes de realizar las operaciones
Sistema de Reintentos Automáticos
AVANZADO

Descripción:

Implementa un decorador que automáticamente reintente una función cuando falle, con backoff exponencial. Debe manejar diferentes tipos de excepciones de manera distinta (algunas se reintentan, otras no) y tener un límite máximo de intentos.

Pista: Usa un decorador con parámetros y time.sleep() para implementar el backoff exponencial

Resumen y Próximos Pasos

El manejo de errores y excepciones es fundamental para crear aplicaciones robustas y profesionales. Hemos cubierto desde conceptos básicos de try-except hasta la creación de excepciones personalizadas y mejores prácticas profesionales. El dominio de estos conceptos permite desarrollar software que puede recuperarse elegantemente de errores y proporcionar mejor experiencia de usuario. En el siguiente tema exploraremos el trabajo con archivos y serialización de datos, donde aplicaremos estos conceptos de manejo de errores en operaciones de entrada/salida que son particularmente propensas a fallos.

🏷️ Etiquetas:
python excepciones manejo-errores try-except programación-defensiva