Try-except en Python: Manejo de Excepciones [Guía completa 2025]

Try except excepciones en python

Contenido

¿Qué son las excepciones en Python?

Las excepciones son eventos que interrumpen el flujo normal de ejecución de un programa cuando ocurre un error. Son el mecanismo de Python para señalar que algo ha salido mal durante la ejecución del código.

Imagina que estás construyendo una calculadora. Si el usuario intenta dividir entre cero, el programa no puede continuar normalmente. En lugar de simplemente cerrarse, Python lanza una excepción que puedes capturar y manejar elegantemente.

Diferencia entre error y excepción:

  • Errores de sintaxis – ocurren antes de que el programa se ejecute (olvidar dos puntos, indentación incorrecta)
  • Excepciones – ocurren durante la ejecución del programa (división entre cero, archivo no encontrado)

Python te permite:

  • Detectar excepciones con try-except
  • Ejecutar código de limpieza con finally
  • Lanzar tus propias excepciones con raise
  • Crear excepciones personalizadas

Manejar excepciones correctamente es la diferencia entre un programa que se rompe inesperadamente y uno que puede recuperarse de errores de forma elegante.


Sintaxis básica: try-except

La estructura try-except te permite intentar ejecutar código que podría fallar, y especificar qué hacer si ocurre un error.

try except python

Sintaxis

try:
    # código que podría lanzar una excepción
    operacion_arriesgada()
except:
    # código que se ejecuta si ocurre cualquier excepción
    manejar_error()

Ejemplo básico – División segura

Vamos a crear una función que divide dos números de forma segura, manejando el caso donde el divisor es cero:

>>> numerator = 10
>>> denominator = 0
>>> 
>>> try:
...     result = numerator / denominator
...     print(f"Resultado: {result}")
... except:
...     print("Error: No se puede dividir entre cero")
Error: No se puede dividir entre cero

Sin el manejo de excepciones, este código causaría un error fatal:

>>> numerator = 10
>>> denominator = 0
>>> result = numerator / denominator
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Flujo de ejecución

Cuando Python encuentra un bloque try-except:

  1. Ejecuta el código dentro del try
  2. Si no ocurre ninguna excepción, salta el bloque except
  3. Si ocurre una excepción, salta el resto del try y ejecuta el except
>>> print("Antes del try")
>>> try:
...     print("Inicio del try")
...     result = 10 / 0  # Esto lanza una excepción
...     print("Esto no se ejecuta")
... except:
...     print("Se capturó una excepción")
>>> print("Después del try-except")
Antes del try
Inicio del try
Se capturó una excepción
Después del try-except

Capturar excepciones específicas

Capturar cualquier excepción con except: (sin especificar tipo) es peligroso porque oculta todos los errores, incluso los que no esperabas. Es mejor capturar excepciones específicas.

Sintaxis

try:
    # código arriesgado
except TipoDeExcepcion:
    # manejar esta excepción específica

Ejemplo básico – Conversión segura de string a número

Cuando convertimos input de usuario a número, debemos manejar el caso donde el usuario ingresa texto no numérico:

>>> user_input = "abc"
>>> 
>>> try:
...     number = int(user_input)
...     print(f"Número válido: {number}")
... except ValueError:
...     print(f"'{user_input}' no es un número válido")
'abc' no es un número válido

Excepciones comunes en Python

ExcepciónCuándo ocurreEjemplo
ValueErrorValor incorrecto para una operaciónint("abc")
TypeErrorOperación con tipo incorrecto"3" + 3
KeyErrorClave no existe en diccionariodict["clave_inexistente"]
IndexErrorÍndice fuera de rangolist[100] cuando solo hay 5 elementos
FileNotFoundErrorArchivo no existeopen("archivo_inexistente.txt")
ZeroDivisionErrorDivisión entre cero10 / 0
AttributeErrorAtributo/método no existestring.metodo_inexistente()
ImportErrorMódulo no se puede importarimport modulo_inexistente

Caso real – Leer archivo de configuración

Cuando lees archivos, pueden ocurrir múltiples errores. Aquí manejamos el caso donde el archivo no existe:

>>> filename = "config.txt"
>>> 
>>> try:
...     with open(filename, 'r') as file:
...         content = file.read()
...         print(f"Contenido: {content}")
... except FileNotFoundError:
...     print(f"Error: El archivo '{filename}' no existe")
...     print("Creando archivo con configuración por defecto...")
...     with open(filename, 'w') as file:
...         file.write("configuracion=default")
Error: El archivo 'config.txt' no existe
Creando archivo con configuración por defecto...

Tip Pro: Nunca uses except: sin especificar el tipo de excepción. Esto puede ocultar bugs y hacer tu código difícil de debuggear. Siempre captura excepciones específicas.


Múltiples bloques except

Cuando tu código puede lanzar diferentes tipos de excepciones, puedes tener múltiples bloques except para manejar cada una de forma diferente.

múltiples excepciones en python

Sintaxis

try:
    # código arriesgado
except TipoExcepcion1:
    # manejar excepción tipo 1
except TipoExcepcion2:
    # manejar excepción tipo 2
except TipoExcepcion3:
    # manejar excepción tipo 3

Ejemplo básico – Calculadora con múltiples validaciones

Vamos a crear una calculadora que maneja varios tipos de errores que pueden ocurrir:

>>> num1 = input("Primer número: ")
>>> num2 = input("Segundo número: ")
>>> operator = input("Operador (+, -, *, /): ")
>>> 
>>> try:
...     number1 = float(num1)
...     number2 = float(num2)
...     
...     if operator == "+":
...         result = number1 + number2
...     elif operator == "-":
...         result = number1 - number2
...     elif operator == "*":
...         result = number1 * number2
...     elif operator == "/":
...         result = number1 / number2
...     else:
...         raise ValueError("Operador no válido")
...     
...     print(f"Resultado: {result}")
... 
... except ValueError as error:
...     print(f"Error de valor: {error}")
... except ZeroDivisionError:
...     print("Error: No se puede dividir entre cero")
... except Exception as error:
...     print(f"Error inesperado: {error}")

Orden de los except

Importante: Python evalúa los bloques except en orden. Siempre pon las excepciones más específicas primero y las más generales al final.

# Incorrecto (Exception captura todo)
>>> try:
...     result = 10 / 0
... except Exception:  # Esto captura TODO
...     print("Error general")
... except ZeroDivisionError:  # Nunca se ejecutará
...     print("División por cero")

# Correcto (específico primero)
>>> try:
...     result = 10 / 0
... except ZeroDivisionError:  # Específico primero
...     print("División por cero")
... except Exception:  # General al final
...     print("Error general")
División por cero

Caso real – Lectura de archivo JSON con validación

Cuando trabajas con archivos JSON, pueden ocurrir varios errores diferentes. Este ejemplo muestra cómo manejarlos todos de forma adecuada:

>>> import json
>>> 
>>> filename = "data.json"
>>> 
>>> try:
...     with open(filename, 'r') as file:
...         data = json.load(file)
...         
...     # Procesar datos
...     user_name = data["name"]
...     user_age = data["age"]
...     
...     print(f"Usuario: {user_name}, Edad: {user_age}")
... 
... except FileNotFoundError:
...     print(f"Error: El archivo '{filename}' no existe")
... except json.JSONDecodeError:
...     print(f"Error: El archivo '{filename}' no contiene JSON válido")
... except KeyError as key:
...     print(f"Error: Falta la clave {key} en el JSON")
... except Exception as error:
...     print(f"Error inesperado: {type(error).__name__}: {error}")

Capturar múltiples excepciones en un solo except

Si quieres manejar varias excepciones de la misma forma, puedes agruparlas en una tupla:

>>> try:
...     # código que puede lanzar ValueError o TypeError
...     value = int(user_input)
... except (ValueError, TypeError):
...     print("Error: Valor o tipo incorrecto")

El bloque else en try-except

El bloque else se ejecuta solo si no ocurrió ninguna excepción en el bloque try. Es útil para separar el código que puede fallar del código que solo debe ejecutarse si todo salió bien.

Sintaxis

try:
    # código que podría fallar
except ExcepcionTipo:
    # manejar excepción
else:
    # código que se ejecuta solo si NO hubo excepción

Ejemplo básico – Confirmación de operación exitosa

Vamos a crear un sistema que confirme cuando una operación se completa sin errores:

>>> try:
...     number = int(input("Ingresa un número: "))
... except ValueError:
...     print("Error: No ingresaste un número válido")
... else:
...     print(f"Perfecto! Ingresaste el número {number}")
...     print("Procesando número...")

Caso real – Guardar configuración

Cuando guardas configuración en un archivo, quieres confirmar el éxito solo si la operación completa sin errores:

>>> config = {"theme": "dark", "language": "es", "notifications": True}
>>> filename = "settings.json"
>>> 
>>> try:
...     import json
...     with open(filename, 'w') as file:
...         json.dump(config, file, indent=2)
... except IOError as error:
...     print(f"Error al guardar configuración: {error}")
... except Exception as error:
...     print(f"Error inesperado: {error}")
... else:
...     print(f"Configuración guardada exitosamente en '{filename}'")
...     print(f"Guardados {len(config)} parámetros")
Configuración guardada exitosamente en 'settings.json'
Guardados 3 parámetros

Diferencia entre else y código después del try:

# Con else
>>> try:
...     result = 10 / 2
... except ZeroDivisionError:
...     print("Error")
... else:
...     print("Éxito")  # Solo si NO hubo error

# Sin else (siempre se ejecuta)
>>> try:
...     result = 10 / 2
... except ZeroDivisionError:
...     print("Error")
>>> print("Siempre se ejecuta")  # Se ejecuta siempre

El bloque finally en Python

El bloque finally se ejecuta siempre, haya ocurrido una excepción o no. Es perfecto para código de limpieza como cerrar archivos, conexiones de base de datos, o liberar recursos.

finally en python

Sintaxis

try:
    # código arriesgado
except ExcepcionTipo:
    # manejar excepción
finally:
    # código que SIEMPRE se ejecuta

Ejemplo básico – Garantizar limpieza

Veamos cómo finally se ejecuta sin importar qué pase:

>>> try:
...     print("Inicio")
...     result = 10 / 0  # Esto lanza excepción
...     print("Esto no se ejecuta")
... except ZeroDivisionError:
...     print("Error capturado")
... finally:
...     print("Finally: Siempre se ejecuta")
Inicio
Error capturado
Finally: Siempre se ejecuta

Incluso sin excepción:

>>> try:
...     print("Inicio")
...     result = 10 / 2
...     print("Operación exitosa")
... except ZeroDivisionError:
...     print("Error")
... finally:
...     print("Finally: Limpieza")
Inicio
Operación exitosa
Finally: Limpieza

Caso real – Cerrar conexión a base de datos

Este es el uso más común de finally: garantizar que los recursos se liberen correctamente, incluso si ocurre un error:

>>> connection = None
>>> 
>>> try:
...     # Simular conexión a base de datos
...     connection = connect_to_database()
...     print("Conexión establecida")
...     
...     # Realizar operación
...     data = connection.query("SELECT * FROM users")
...     process_data(data)
...     
... except ConnectionError:
...     print("Error: No se pudo conectar a la base de datos")
... except QueryError as error:
...     print(f"Error en la consulta: {error}")
... finally:
...     if connection:
...         connection.close()
...         print("Conexión cerrada correctamente")

Caso avanzado – Medidor de tiempo de ejecución

Podemos usar finally para medir cuánto tiempo tomó ejecutar código, sin importar si fue exitoso o no:

>>> import time
>>> 
>>> start_time = time.time()
>>> 
>>> try:
...     print("Procesando datos...")
...     # Simular procesamiento
...     result = heavy_computation()
...     print(f"Resultado: {result}")
... except Exception as error:
...     print(f"Error durante el procesamiento: {error}")
... finally:
...     elapsed_time = time.time() - start_time
...     print(f"Tiempo total: {elapsed_time:.2f} segundos")

Lanzar excepciones con raise

Además de capturar excepciones, puedes crear y lanzar tus propias excepciones usando raise. Esto es útil cuando detectas una condición de error en tu código.

Sintaxis

raise TipoExcepcion("mensaje de error")

Ejemplo básico – Validación de edad

Vamos a validar que una edad esté en un rango válido y lanzar una excepción si no lo está:

>>> def validate_age(age):
...     if age < 0:
...         raise ValueError("La edad no puede ser negativa")
...     if age > 150:
...         raise ValueError("La edad no puede ser mayor a 150")
...     return True
>>> 
>>> try:
...     age = -5
...     validate_age(age)
...     print("Edad válida")
... except ValueError as error:
...     print(f"Error de validación: {error}")
Error de validación: La edad no puede ser negativa

Re-lanzar excepciones

A veces quieres capturar una excepción, hacer algo (como logging), y luego re-lanzarla para que sea manejada en otro lugar:

>>> def process_data(data):
...     try:
...         result = dangerous_operation(data)
...         return result
...     except Exception as error:
...         print(f"Error registrado: {error}")
...         raise  # Re-lanza la misma excepción
>>> 
>>> try:
...     result = process_data(invalid_data)
... except Exception:
...     print("Error manejado en nivel superior")

Caso real – Validación de datos de usuario

En una aplicación real, necesitas validar múltiples campos y proporcionar mensajes de error claros:

>>> def register_user(username, email, password, age):
...     # Validar username
...     if len(username) < 3:
...         raise ValueError("El username debe tener al menos 3 caracteres")
...     
...     # Validar email
...     if "@" not in email or "." not in email:
...         raise ValueError("Email inválido")
...     
...     # Validar contraseña
...     if len(password) < 8:
...         raise ValueError("La contraseña debe tener al menos 8 caracteres")
...     
...     # Validar edad
...     if age < 18:
...         raise ValueError("Debes ser mayor de 18 años")
...     
...     return {"username": username, "email": email, "age": age}
>>> 
>>> try:
...     user = register_user("jo", "invalid_email", "123", 15)
...     print(f"Usuario registrado: {user}")
... except ValueError as error:
...     print(f"Error de registro: {error}")
Error de registro: El username debe tener al menos 3 caracteres

Llevando el manejo de excepciones al siguiente nivel

En el libro Python a fondo encontrarás patrones avanzados de manejo de excepciones, arquitectura de error handling y técnicas para escribir código robusto y mantenible.

Ver el libro


Crear excepciones personalizadas

Python te permite crear tus propias excepciones heredando de la clase Exception. Esto es útil cuando quieres excepciones específicas para tu aplicación.

Sintaxis básica

class MiExcepcionPersonalizada(Exception):
    pass

Ejemplo básico – Excepción de validación

Vamos a crear una excepción personalizada para validación de datos:

>>> class ValidationError(Exception):
...     """Excepción lanzada cuando la validación falla"""
...     pass
>>> 
>>> def validate_username(username):
...     if len(username) < 3:
...         raise ValidationError("Username muy corto")
...     if not username.isalnum():
...         raise ValidationError("Username debe ser alfanumérico")
...     return True
>>> 
>>> try:
...     validate_username("ab")
... except ValidationError as error:
...     print(f"Error: {error}")
Error: Username muy corto

Excepciones con datos adicionales

Puedes hacer excepciones más sofisticadas que almacenen información adicional:

>>> class InsufficientFundsError(Exception):
...     """Excepción lanzada cuando no hay fondos suficientes"""
...     def __init__(self, balance, amount):
...         self.balance = balance
...         self.amount = amount
...         self.shortage = amount - balance
...         message = f"Fondos insuficientes. Balance: ${balance}, Intentó retirar: ${amount}"
...         super().__init__(message)
>>> 
>>> def withdraw(balance, amount):
...     if amount > balance:
...         raise InsufficientFundsError(balance, amount)
...     return balance - amount
>>> 
>>> try:
...     current_balance = 100
...     new_balance = withdraw(current_balance, 150)
... except InsufficientFundsError as error:
...     print(f"Error: {error}")
...     print(f"Le faltan: ${error.shortage}")
Error: Fondos insuficientes. Balance: $100, Intentó retirar: $150
Le faltan: $50

Caso real – Sistema de excepciones para API

En una aplicación API real, puedes crear una jerarquía de excepciones personalizadas:

>>> class APIError(Exception):
...     """Clase base para errores de API"""
...     pass
>>> 
>>> class AuthenticationError(APIError):
...     """Error de autenticación"""
...     pass
>>> 
>>> class AuthorizationError(APIError):
...     """Error de autorización (permisos)"""
...     pass
>>> 
>>> class ResourceNotFoundError(APIError):
...     """Recurso no encontrado"""
...     pass
>>> 
>>> class RateLimitError(APIError):
...     """Límite de peticiones excedido"""
...     def __init__(self, retry_after):
...         self.retry_after = retry_after
...         super().__init__(f"Límite excedido. Reintentar en {retry_after} segundos")
>>> 
>>> def api_request(endpoint, token):
...     if not token:
...         raise AuthenticationError("Token requerido")
...     
...     if not is_valid_token(token):
...         raise AuthenticationError("Token inválido")
...     
...     if not has_permission(token, endpoint):
...         raise AuthorizationError("Sin permisos para este recurso")
...     
...     if rate_limit_exceeded(token):
...         raise RateLimitError(retry_after=60)
...     
...     # Procesar petición...
...     return {"status": "success", "data": {}}
>>> 
>>> try:
...     response = api_request("/users", token=None)
... except AuthenticationError as error:
...     print(f"Auth error: {error}")
... except AuthorizationError as error:
...     print(f"Permission error: {error}")
... except RateLimitError as error:
...     print(f"Rate limit: {error}")
...     print(f"Reintentar en: {error.retry_after}s")
... except APIError as error:
...     print(f"API error: {error}")
Auth error: Token requerido

Mejores prácticas en manejo de excepciones

1. Sé específico en lo que capturas

# Malo (captura TODO)
>>> try:
...     result = risky_operation()
... except:  # Nunca hagas esto
...     print("Error")

# Bueno (específico)
>>> try:
...     result = risky_operation()
... except ValueError:
...     print("Valor inválido")
... except TypeError:
...     print("Tipo incorrecto")

2. No uses excepciones para control de flujo

Las excepciones son para situaciones excepcionales, no para lógica normal:

# Malo (usar excepción para lógica normal)
>>> try:
...     value = dictionary[key]
... except KeyError:
...     value = default_value

# Mejor (usar get)
>>> value = dictionary.get(key, default_value)

3. Proporciona mensajes de error útiles

# Malo (mensaje genérico)
>>> raise ValueError("Error")

# Bueno (mensaje descriptivo)
>>> raise ValueError(f"El valor {value} debe estar entre {min_val} y {max_val}")

4. No silencies excepciones

# Muy malo (oculta errores)
>>> try:
...     important_operation()
... except:
...     pass  # ¡Nunca hagas esto!

# Bueno (al menos registra el error)
>>> try:
...     important_operation()
... except Exception as error:
...     logger.error(f"Error en operación: {error}")
...     raise  # Re-lanza para que se maneje arriba

5. Limpia recursos con finally o context managers

# Bueno (usar finally)
>>> file = open("data.txt")
>>> try:
...     process(file)
... finally:
...     file.close()

# Mejor (context manager)
>>> with open("data.txt") as file:
...     process(file)  # Se cierra automáticamente

Jerarquía de excepciones en Python

Python organiza las excepciones en una jerarquía. Conocer esta jerarquía te ayuda a capturar excepciones de forma más efectiva.

jerarquia de excepciones en python

Jerarquía principal

BaseException
├── SystemExit
├── KeyboardInterrupt
├── Exception
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   ├── OverflowError
    │   └── FloatingPointError
    ├── AttributeError
    ├── EOFError
    ├── ImportError
    │   └── ModuleNotFoundError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── NameError
    ├── OSError
    │   ├── FileNotFoundError
    │   ├── PermissionError
    │   └── TimeoutError
    ├── TypeError
    ├── ValueError
    └── RuntimeError

Por qué importa la jerarquía

Cuando capturas una excepción padre, también capturas todas las excepciones hijas:

>>> try:
...     file = open("nonexistent.txt")
... except OSError:  # Captura FileNotFoundError, PermissionError, etc.
...     print("Error de sistema operativo")

Lista completa de excepciones built-in más comunes

ExcepciónDescripciónCaso de uso
ExceptionClase base para todas las excepciones no fatalesCaptura general (úsala con cuidado)
ValueErrorValor correcto de tipo pero inapropiadoint("abc"), validación de rangos
TypeErrorOperación con tipo incorrecto"texto" + 5
KeyErrorClave no existe en diccionariodict["clave_inexistente"]
IndexErrorÍndice fuera de rango de secuencialista[999]
AttributeErrorAtributo o método no existeobj.metodo_inexistente()
FileNotFoundErrorArchivo no encontradoopen("archivo_inexistente.txt")
PermissionErrorSin permisos para operaciónIntentar escribir archivo protegido
ZeroDivisionErrorDivisión o módulo por cero10 / 0
ImportErrorFallo al importar móduloimport modulo_inexistente
ModuleNotFoundErrorMódulo no encontrado (Python 3.6+)import libreria_no_instalada
NameErrorVariable no definidaUsar variable antes de definirla
RuntimeErrorError que no cae en otras categoríasErrores genéricos en runtime
StopIterationIterador no tiene más elementosUsado internamente por iteradores
AssertionErrorFallo en sentencia assertassert False, "Error"

Casos de uso reales

Caso 1: Sistema robusto de carga de configuración

Este ejemplo muestra cómo manejar múltiples posibles errores al cargar configuración desde un archivo:

>>> import json
>>> import os
>>> 
>>> def load_config(filename="config.json"):
...     """Carga configuración desde archivo JSON con manejo robusto de errores"""
...     default_config = {
...         "theme": "light",
...         "language": "en",
...         "notifications": True
...     }
...     
...     try:
...         # Verificar si archivo existe
...         if not os.path.exists(filename):
...             print(f"Archivo {filename} no existe. Usando configuración por defecto.")
...             return default_config
...         
...         # Intentar abrir y leer archivo
...         with open(filename, 'r') as file:
...             config = json.load(file)
...         
...         # Validar que es un diccionario
...         if not isinstance(config, dict):
...             raise TypeError("La configuración debe ser un diccionario")
...         
...         print(f"Configuración cargada desde {filename}")
...         return config
...     
...     except json.JSONDecodeError as error:
...         print(f"Error: El archivo contiene JSON inválido: {error}")
...         print("Usando configuración por defecto.")
...         return default_config
...     
...     except PermissionError:
...         print(f"Error: Sin permisos para leer {filename}")
...         return default_config
...     
...     except Exception as error:
...         print(f"Error inesperado al cargar configuración: {error}")
...         return default_config
>>> 
>>> config = load_config()
>>> print(f"Configuración activa: {config}")

Caso 2: Retry logic con manejo de excepciones

Cuando trabajas con servicios externos, a veces necesitas reintentar operaciones que fallan temporalmente:

>>> import time
>>> 
>>> def fetch_data_with_retry(url, max_retries=3):
...     """Intenta obtener datos con reintentos automáticos"""
...     for attempt in range(1, max_retries + 1):
...         try:
...             print(f"Intento {attempt} de {max_retries}...")
...             
...             # Simular petición a API externa
...             response = make_http_request(url)
...             
...             # Si llegamos aquí, la petición fue exitosa
...             print("Petición exitosa")
...             return response
...         
...         except ConnectionError as error:
...             print(f"Error de conexión: {error}")
...             
...             if attempt < max_retries:
...                 wait_time = attempt * 2  # Backoff exponencial
...                 print(f"Reintentando en {wait_time} segundos...")
...                 time.sleep(wait_time)
...             else:
...                 print("Máximo de reintentos alcanzado")
...                 raise  # Re-lanza después de todos los intentos
...         
...         except TimeoutError:
...             print("Timeout: El servidor no respondió a tiempo")
...             if attempt < max_retries:
...                 print("Reintentando...")
...             else:
...                 raise
>>> 
>>> try:
...     data = fetch_data_with_retry("https://api.example.com/data")
...     print(f"Datos obtenidos: {data}")
... except Exception as error:
...     print(f"No se pudieron obtener los datos después de todos los intentos")

Caso 3: Validación completa de formulario

Este ejemplo muestra cómo validar múltiples campos y recopilar todos los errores para mostrarlos al usuario:

>>> class ValidationError(Exception):
...     """Excepción para errores de validación con múltiples mensajes"""
...     def __init__(self, errors):
...         self.errors = errors
...         super().__init__(f"Validación falló: {len(errors)} errores")
>>> 
>>> def validate_registration_form(data):
...     """Valida formulario de registro y recopila todos los errores"""
...     errors = []
...     
...     # Validar username
...     username = data.get("username", "")
...     if len(username) < 3:
...         errors.append("Username debe tener al menos 3 caracteres")
...     elif len(username) > 20:
...         errors.append("Username no puede exceder 20 caracteres")
...     elif not username.isalnum():
...         errors.append("Username solo puede contener letras y números")
...     
...     # Validar email
...     email = data.get("email", "")
...     if not email:
...         errors.append("Email es requerido")
...     elif "@" not in email or "." not in email:
...         errors.append("Email inválido")
...     
...     # Validar password
...     password = data.get("password", "")
...     if len(password) < 8:
...         errors.append("Contraseña debe tener al menos 8 caracteres")
...     elif not any(char.isdigit() for char in password):
...         errors.append("Contraseña debe contener al menos un número")
...     elif not any(char.isupper() for char in password):
...         errors.append("Contraseña debe contener al menos una mayúscula")
...     
...     # Validar age
...     age = data.get("age")
...     if age is None:
...         errors.append("Edad es requerida")
...     elif not isinstance(age, int):
...         errors.append("Edad debe ser un número")
...     elif age < 18:
...         errors.append("Debes ser mayor de 18 años")
...     elif age > 120:
...         errors.append("Edad no válida")
...     
...     # Si hay errores, lanzar excepción con todos
...     if errors:
...         raise ValidationError(errors)
...     
...     return True
>>> 
>>> # Probar validación
>>> user_data = {
...     "username": "ab",
...     "email": "invalid_email",
...     "password": "short",
...     "age": 15
... }
>>> 
>>> try:
...     validate_registration_form(user_data)
...     print("Registro exitoso")
... except ValidationError as error:
...     print("Errores de validación:")
...     for i, err in enumerate(error.errors, 1):
...         print(f"  {i}. {err}")
Errores de validación:
  1. Username debe tener al menos 3 caracteres
  2. Email inválido
  3. Contraseña debe tener al menos 8 caracteres
  4. Contraseña debe contener al menos un número
  5. Contraseña debe contener al menos una mayúscula
  6. Debes ser mayor de 18 años

Caso 4: Context manager personalizado con manejo de excepciones

Los context managers (usado con with) pueden tener manejo de excepciones integrado:

>>> class DatabaseConnection:
...     """Context manager para manejar conexiones a base de datos"""
...     def __init__(self, host, database):
...         self.host = host
...         self.database = database
...         self.connection = None
...     
...     def __enter__(self):
...         """Se ejecuta al entrar al bloque with"""
...         try:
...             print(f"Conectando a {self.database} en {self.host}...")
...             # Simular conexión
...             self.connection = connect_to_db(self.host, self.database)
...             print("Conexión establecida")
...             return self.connection
...         except ConnectionError as error:
...             print(f"Error al conectar: {error}")
...             raise
...     
...     def __exit__(self, exc_type, exc_value, traceback):
...         """Se ejecuta al salir del bloque with (incluso si hay error)"""
...         if self.connection:
...             print("Cerrando conexión...")
...             self.connection.close()
...             print("Conexión cerrada")
...         
...         # Si hubo excepción en el bloque with
...         if exc_type is not None:
...             print(f"Error durante operación: {exc_type.__name__}: {exc_value}")
...             # Return False para propagar la excepción
...             # Return True para suprimir la excepción
...             return False
>>> 
>>> # Usar el context manager
>>> try:
...     with DatabaseConnection("localhost", "mydb") as db:
...         print("Ejecutando query...")
...         result = db.query("SELECT * FROM users")
...         print(f"Resultados: {result}")
... except Exception as error:
...     print(f"Operación falló: {error}")

Errores comunes al manejar excepciones

Error 1: Capturar excepciones demasiado generales

# Muy malo (captura TODO, incluso KeyboardInterrupt)
>>> try:
...     operation()
... except BaseException:  # Nunca uses esto
...     pass

# Malo (captura demasiado)
>>> try:
...     operation()
... except Exception:  # Demasiado general
...     pass

# Bueno (específico)
>>> try:
...     operation()
... except (ValueError, TypeError) as error:
...     handle_error(error)

Error 2: Except vacío sin hacer nada

# Muy malo (oculta el error completamente)
>>> try:
...     risky_operation()
... except:
...     pass  # Silencioso y peligroso

# Mejor (al menos registra)
>>> try:
...     risky_operation()
... except Exception as error:
...     logger.error(f"Error: {error}")
...     # O decidir qué hacer con el error

Error 3: Usar excepciones para lógica normal

# Malo (excepción como control de flujo)
>>> try:
...     index = items.index(target)
... except ValueError:
...     index = -1

# Mejor (usar in)
>>> index = items.index(target) if target in items else -1

# O mejor aún (usar find para strings)
>>> index = text.find(substring)  # Retorna -1 si no encuentra

Error 4: No proporcionar contexto en el error

# Malo (error sin contexto)
>>> if value < 0:
...     raise ValueError("Valor inválido")

# Bueno (error con contexto útil)
>>> if value < 0:
...     raise ValueError(f"El valor {value} no puede ser negativo. Debe ser >= 0")

Error 5: Re-lanzar excepción incorrectamente

# Malo (pierde el stack trace original)
>>> try:
...     operation()
... except Exception as error:
...     raise Exception(str(error))

# Bueno (preserva el stack trace)
>>> try:
...     operation()
... except Exception as error:
...     logger.error(f"Error: {error}")
...     raise  # Re-lanza la excepción original

Error 6: Finally que modifica el retorno

# Peligroso (finally sobreescribe el return)
>>> def risky_function():
...     try:
...         return "valor del try"
...     finally:
...         return "valor del finally"  # ¡Esto sobreescribe!
>>> 
>>> risky_function()
'valor del finally'  # No es lo que esperabas

# Correcto (finally no interfiere con return)
>>> def safe_function():
...     try:
...         return "valor del try"
...     finally:
...         cleanup()  # Solo limpieza, sin return

Logging de excepciones

Registrar excepciones es crucial para debugging y monitoreo. Aquí te muestro las mejores prácticas:

Usar el módulo logging

>>> import logging
>>> 
>>> # Configurar logging
>>> logging.basicConfig(
...     level=logging.INFO,
...     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
... )
>>> logger = logging.getLogger(__name__)
>>> 
>>> def process_payment(amount):
...     try:
...         if amount <= 0:
...             raise ValueError("El monto debe ser positivo")
...         
...         # Procesar pago
...         result = charge_card(amount)
...         logger.info(f"Pago procesado: ${amount}")
...         return result
...     
...     except ValueError as error:
...         logger.error(f"Error de validación: {error}")
...         raise
...     
...     except PaymentError as error:
...         logger.error(f"Error al procesar pago de ${amount}: {error}")
...         raise
...     
...     except Exception as error:
...         logger.exception("Error inesperado al procesar pago")
...         raise

Usar logger.exception() para stack trace completo

>>> try:
...     result = complex_operation()
... except Exception as error:
...     # exception() automáticamente incluye el stack trace
...     logger.exception("Error en operación compleja")
...     # Equivalente a:
...     # logger.error("Error en operación compleja", exc_info=True)

Puntos clave para recordar

El manejo de excepciones es lo que separa el código amateur del código profesional. Dominar excepciones te permite escribir aplicaciones robustas que fallan elegantemente.

Pepitas de conocimiento

  1. Excepciones vs errores de sintaxis – Las excepciones ocurren en tiempo de ejecución, los errores de sintaxis antes de ejecutar
  2. Sé específico al capturar – Captura solo las excepciones que sabes manejar
  3. El orden importa – Excepciones específicas primero, generales al final
  4. else se ejecuta si no hay excepción – Útil para separar código que puede fallar del que solo se ejecuta si todo va bien
  5. finally siempre se ejecuta – Perfecto para limpieza de recursos
  6. raise para validaciones – Lanza excepciones cuando detectes condiciones inválidas
  7. Excepciones personalizadas – Crea tus propias excepciones heredando de Exception
  8. No uses excepciones para control de flujo – Son para situaciones excepcionales
  9. Proporciona contexto en los errores – Mensajes descriptivos ayudan al debugging
  10. Registra excepciones – Usa logging para rastrear errores en producción
  11. Re-lanza con raise – Preserva el stack trace original
  12. Context managers para recursos – Garantizan limpieza incluso con errores

Próximos pasos

¡Felicidades! Has completado el Módulo 3 – Control de flujo de ejecución. Ahora dominas:

  • Operadores y expresiones
  • Estructuras condicionales (if/elif/else)
  • Bucles (for y while)
  • Manejo de excepciones

El siguiente paso es aprender sobre funciones en Python, que te permitirán organizar y reutilizar tu código de forma eficiente.


Siguiente paso: Funciones en Python

Ya dominas el control de flujo. Ahora aprende a organizar tu código con funciones:

Funciones en Python – Guía completa


¿Te ha gustado este artículo? Compártelo con otros pythonistas que estén aprendiendo a programar.

Déjanos un comentario con tus dudas sobre excepciones o comparte cómo manejas errores en tus proyectos.

Para profundizar más con patrones avanzados de manejo de excepciones y arquitecturas robustas, no te pierdas Python a fondo.

Compartir

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Información básica sobre protección de datos Ver más

  • Responsable: Oscar Ramirez.
  • Finalidad:  Moderar los comentarios.
  • Legitimación:  Por consentimiento del interesado.
  • Destinatarios y encargados de tratamiento: No se ceden o comunican datos a terceros para prestar este servicio. El Titular ha contratado los servicios de alojamiento web a ionos (1&1) que actúa como encargado de tratamiento.
  • Derechos: Acceder, rectificar y suprimir los datos.
  • Información Adicional: Puede consultar la información detallada en la Política de Privacidad.

Publicar un comentario

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para fines de afiliación y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Ver Política de cookies
Privacidad