Manejo de excepciones en Python — try, except, raise
Al finalizar este tema
Comprenderá el principio subyacente a la aparición de errores en Python, aprenderá a capturarlos mediante try/except, generar errores directamente con raise y realizar tareas de limpieza utilizando finally.
Los errores detienen el programa
En Python, cuando ocurre un error, el programa se detiene inmediatamente.
numbers = [1, 2, 3]print(numbers[10]) # IndexError: list index out of rangeprint("This line never runs")Si se produce un error, todo el código posterior no se ejecuta. En un servidor web, el propio servidor puede dejar de funcionar. En un script de análisis de datos, podrías procesar 999 de cada 1000 registros, pero uno solo provoca que falle todo el proceso.
El manejo de excepciones es "la forma de responder sin detenerse cuando ocurre un error".
try/except — Capturar errores
try: result = 10 / 0except ZeroDivisionError: print("No se puede dividir entre cero")
print("El programa continúa") # ¡Esta línea se ejecuta!Si ocurre un error dentro del bloque try, el programa salta al bloque except. El programa continúa ejecutándose sin detenerse.
Obtener mensaje de error
try: value = int("hello")except ValueError as e: print(f"Error: {e}") # Error: invalid literal for int() with base 10: 'hello'Al recibir un objeto de error con as e, puedes verificar qué salió mal.
Manejo individual de cada error
def safe_divide(a, b): try: result = a / b return round(result, 2) except ZeroDivisionError: print("Denominator cannot be zero") return None except TypeError: print("Both arguments must be numbers") return None
safe_divide(10, 0) # Denominator cannot be zerosafe_divide("10", 2) # Both arguments must be numberssafe_divide(10, 3) # 3.33Puede manejar diferentes tipos de errores con respuestas distintas. Aunque es posible capturar todos los errores de una vez con except Exception, no se recomienda hacerlo, ya que sin saber qué tipo de error ocurrió, la depuración se vuelve difícil.
Tipos de errores comunes
| Error | Causa | Ejemplo |
|---|---|---|
ValueError | Valor incorrecto | int("abc") |
TypeError | Tipo incorrecto | "3" + 5 |
IndexError | Índice fuera de rango | [1,2,3][10] |
KeyError | Clave no encontrada en el diccionario | {"a": 1}["b"] |
FileNotFoundError | Archivo no encontrado | open("none.txt") |
ZeroDivisionError | División por cero | 10 / 0 |
AttributeError | Acceso a atributo/método inexistente | None.upper() |
Estos siete son los errores más comunes que se encuentran en el día a día.
else y finally
try: f = open("data.txt", "r") content = f.read()except FileNotFoundError: print("File not found")else: # Se ejecuta solo si try tiene éxito print(f"Read {len(content)} characters")finally: # Se ejecuta siempre, haya éxito o fallo print("Cleanup done")| Bloque | Momento de ejecución |
|---|---|
try | Siempre se intenta |
except | Cuando ocurre un error |
else | Cuando tiene éxito sin errores |
finally | Sin importar el resultado (tanto en éxito como en fallo) |
finally se utiliza para tareas de limpieza como cerrar archivos, finalizar conexiones de red y eliminar archivos temporales. Es código que debe ejecutarse obligatoriamente, independientemente de si ocurre un error o no.
raise — Generar un error directamente
def withdraw(balance, amount): if amount <= 0: raise ValueError("Withdrawal amount must be positive") if amount > balance: raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}") return balance - amount
try: new_balance = withdraw(1000, 5000)except ValueError as e: print(f"Transaction failed: {e}") # Transaction failed: Insufficient funds: balance=1000, requested=5000raise es declarar "esta situación no es normal". Transfiere la responsabilidad al llamador para que lo maneje mediante try/except.
Clase de error personalizada
class InsufficientFundsError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__( f"Cannot withdraw {amount} from balance {balance}" )
def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(balance, amount) return balance - amount
try: withdraw(1000, 5000)except InsufficientFundsError as e: print(e) # Cannot withdraw 5000 from balance 1000 print(e.balance) # 1000 print(e.amount) # 5000Exception se puede heredar para crear tus propios errores. Puedes incluir información adicional (saldo, monto de la solicitud) en el error, lo que permite que quien lo capture pueda manejarlo con mayor precisión.
Patrón práctico — Procesamiento de archivos
def read_config(filepath): try: with open(filepath, "r", encoding="utf-8") as f: lines = f.readlines() except FileNotFoundError: print(f"Config file not found: {filepath}") return {} except PermissionError: print(f"Permission denied: {filepath}") return {}
config = {} for i, line in enumerate(lines, 1): line = line.strip() if not line or line.startswith("#"): continue if "=" not in line: print(f"Warning: invalid format at line {i}: {line}") continue key, value = line.split("=", 1) config[key.strip()] = value.strip()
return config
# config.txt:# host = localhost# port = 3000# # this is a commentsettings = read_config("config.txt")print(settings) # {'host': 'localhost', 'port': '3000'}Este patrón se utiliza con mucha frecuencia en la práctica:
- Manejar el fallo al abrir un archivo como
except - Emitir solo una advertencia y omitir las filas incorrectas durante el análisis
- Ignorar las líneas vacías y los comentarios (
#)
En lugar de que el programa falle simplemente por no poder abrir un archivo de configuración, este opera con valores predeterminados o muestra una advertencia.
Antipatrón — Lo que no se debe hacer
Ignorar todos los errores
# Mal — se traga el errortry: risky_operation()except Exception: pass # What happened? Nobody knows
# Bien — al menos deja un registrotry: risky_operation()except Exception as e: print(f"Warning: {e}") # or logging.warning(...)except: pass ignora "todos los errores". El programa no se detiene, pero no hay forma de saber qué salió mal. Más adelante, al depurar, será muy difícil encontrar la causa raíz.
Bloque except demasiado amplio
# Mal — también captura un error tipográfico (NameError)try: valeu = int(input("Enter number: ")) # typo: valeuexcept Exception: print("Entrada no válida") # ¿"Entrada no válida" aunque sea un NameError?
# Bien — capturar solo los errores previstostry: value = int(input("Enter number: "))except ValueError: print("Invalid input — please enter a number")Resumen clave
| Sintaxis | Función | Cuándo usarla |
|---|---|---|
try | Envolver código que puede generar errores | Entradas externas, archivos, redes, etc. |
except | Capturar un error específico y responderlo | Cuando se requiere un manejo distinto según el tipo de error |
else | Ejecutar solo en caso de éxito | Tareas adicionales cuando no hay errores |
finally | Ejecutar siempre | Tareas de limpieza (cerrar archivos, cerrar conexiones) |
raise | Generar un error directamente | Entradas inválidas, violación de reglas de negocio |
El principio fundamental del manejo de excepciones es: capturar solo los errores esperados, hacerlo lo más específicamente posible y nunca ignorarlos. El pensamiento "envolverlo todo con try/except para estar seguro" es un error. Un manejo incorrecto de excepciones oculta los errores y dificulta la depuración incluso más que si no hubiera errores.