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

Contenido
- 1 ¿Qué son las excepciones en Python?
- 2 Sintaxis básica: try-except
- 3 Capturar excepciones específicas
- 4 Múltiples bloques except
- 5 El bloque else en try-except
- 6 El bloque finally en Python
- 7 Lanzar excepciones con raise
- 8 Crear excepciones personalizadas
- 9 Mejores prácticas en manejo de excepciones
- 10 Jerarquía de excepciones en Python
- 11 Casos de uso reales
- 12 Errores comunes al manejar excepciones
- 13 Logging de excepciones
- 14 Puntos clave para recordar
¿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.

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 ceroSin 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 zeroFlujo de ejecución
Cuando Python encuentra un bloque try-except:
- Ejecuta el código dentro del
try - Si no ocurre ninguna excepción, salta el bloque
except - Si ocurre una excepción, salta el resto del
tryy ejecuta elexcept
>>> 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-exceptCapturar 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íficaEjemplo 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álidoExcepciones comunes en Python
| Excepción | Cuándo ocurre | Ejemplo |
|---|---|---|
ValueError | Valor incorrecto para una operación | int("abc") |
TypeError | Operación con tipo incorrecto | "3" + 3 |
KeyError | Clave no existe en diccionario | dict["clave_inexistente"] |
IndexError | Índice fuera de rango | list[100] cuando solo hay 5 elementos |
FileNotFoundError | Archivo no existe | open("archivo_inexistente.txt") |
ZeroDivisionError | División entre cero | 10 / 0 |
AttributeError | Atributo/método no existe | string.metodo_inexistente() |
ImportError | Módulo no se puede importar | import 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.

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 3Ejemplo 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 ceroCaso 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ónEjemplo 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ámetrosDiferencia 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 siempreEl 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.

Sintaxis
try:
# código arriesgado
except ExcepcionTipo:
# manejar excepción
finally:
# código que SIEMPRE se ejecutaEjemplo 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 ejecutaIncluso 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: LimpiezaCaso 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 negativaRe-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 caracteresLlevando 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.
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):
passEjemplo 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 cortoExcepciones 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: $50Caso 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 requeridoMejores 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 arriba5. 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áticamenteJerarquí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.

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
└── RuntimeErrorPor 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ón | Descripción | Caso de uso |
|---|---|---|
Exception | Clase base para todas las excepciones no fatales | Captura general (úsala con cuidado) |
ValueError | Valor correcto de tipo pero inapropiado | int("abc"), validación de rangos |
TypeError | Operación con tipo incorrecto | "texto" + 5 |
KeyError | Clave no existe en diccionario | dict["clave_inexistente"] |
IndexError | Índice fuera de rango de secuencia | lista[999] |
AttributeError | Atributo o método no existe | obj.metodo_inexistente() |
FileNotFoundError | Archivo no encontrado | open("archivo_inexistente.txt") |
PermissionError | Sin permisos para operación | Intentar escribir archivo protegido |
ZeroDivisionError | División o módulo por cero | 10 / 0 |
ImportError | Fallo al importar módulo | import modulo_inexistente |
ModuleNotFoundError | Módulo no encontrado (Python 3.6+) | import libreria_no_instalada |
NameError | Variable no definida | Usar variable antes de definirla |
RuntimeError | Error que no cae en otras categorías | Errores genéricos en runtime |
StopIteration | Iterador no tiene más elementos | Usado internamente por iteradores |
AssertionError | Fallo en sentencia assert | assert 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ñosCaso 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 errorError 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 encuentraError 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 originalError 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 returnLogging 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")
... raiseUsar 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
- Excepciones vs errores de sintaxis – Las excepciones ocurren en tiempo de ejecución, los errores de sintaxis antes de ejecutar
- Sé específico al capturar – Captura solo las excepciones que sabes manejar
- El orden importa – Excepciones específicas primero, generales al final
- 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
- finally siempre se ejecuta – Perfecto para limpieza de recursos
- raise para validaciones – Lanza excepciones cuando detectes condiciones inválidas
- Excepciones personalizadas – Crea tus propias excepciones heredando de Exception
- No uses excepciones para control de flujo – Son para situaciones excepcionales
- Proporciona contexto en los errores – Mensajes descriptivos ayudan al debugging
- Registra excepciones – Usa logging para rastrear errores en producción
- Re-lanza con raise – Preserva el stack trace original
- 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.
