Todo programa falla en algún momento. Un usuario escribe texto donde se espera un número, un archivo no existe, una conexión se cae. En Python, el manejo de errores te permite anticiparte a esas situaciones y controlar qué hace tu programa cuando algo sale mal, en lugar de que simplemente se rompa.
En esta guía aprenderás a usar try, except, else, finally, a lanzar tus propios errores con raise, y a depurar tu código de forma efectiva.
1. ¿Qué es una excepción?
Una excepción es un error que ocurre durante la ejecución de un programa. Cuando Python encuentra un error que no puede resolver, lanza (o "levanta") una excepción, detiene el flujo normal y muestra un mensaje de error llamado traceback.
Por ejemplo, si intentas dividir por cero:
print(10 / 0)
Python responde con:
Traceback (most recent call last):
File "script.py", line 1, in <module>
print(10 / 0)
ZeroDivisionError: division by zero
El mensaje tiene tres partes importantes: el archivo y la línea donde ocurrió el error, el nombre de la excepción (ZeroDivisionError) y una descripción del problema. Aprender a leer el traceback es la habilidad más valiosa para depurar código.
La diferencia entre un error de sintaxis y una excepción es que los errores de sintaxis impiden que el programa inicie (Python no puede leer el código), mientras que las excepciones ocurren durante la ejecución.
2. La estructura básica: try / except
La forma de manejar excepciones en Python es con el bloque try / except. El código que puede fallar va dentro de try, y lo que debe ocurrir si falla va dentro de except.
try:
numero = int(input("Escribe un número: "))
resultado = 10 / numero
print(f"Resultado: {resultado}")
except ZeroDivisionError:
print("Error: no puedes dividir por cero.")
except ValueError:
print("Error: debes escribir un número entero.")
Si el usuario escribe 0, Python ejecuta el bloque except ZeroDivisionError. Si escribe hola, ejecuta el bloque except ValueError. En ninguno de los dos casos el programa se rompe.
También puedes capturar cualquier excepción sin especificar el tipo, aunque no es la práctica recomendada porque oculta errores inesperados:
try:
resultado = 10 / 0
except Exception as e:
print(f"Ocurrió un error: {e}")
El parámetro as e guarda el objeto de la excepción en la variable e, lo que te permite mostrar o registrar el mensaje de error original.
3. Capturar múltiples excepciones
Puedes encadenar tantos bloques except como necesites, uno por cada tipo de error que quieras tratar de forma diferente. Python los recorre en orden y ejecuta el primero que coincida.
def dividir(a, b):
try:
return a / b
except ZeroDivisionError:
print("No se puede dividir por cero.")
return None
except TypeError:
print("Los argumentos deben ser números.")
return None
dividir(10, 0) # No se puede dividir por cero.
dividir(10, "dos") # Los argumentos deben ser números.
dividir(10, 2) # 5.0
Si quieres tratar varios errores de la misma manera, puedes agruparlos en una tupla dentro de un solo except:
try:
valor = int(input("Número: "))
print(10 / valor)
except (ValueError, ZeroDivisionError) as e:
print(f"Entrada inválida: {e}")
4. else y finally: el ciclo completo
La estructura completa de manejo de errores en Python tiene cuatro bloques opcionales:
try:
# código que puede fallar
except TipoDeError:
# se ejecuta si ocurre el error
else:
# se ejecuta SOLO si no hubo ningún error
finally:
# se ejecuta SIEMPRE, haya error o no
Un ejemplo práctico abriendo un archivo:
archivo = None
try:
archivo = open("datos.txt", "r")
contenido = archivo.read()
except FileNotFoundError:
print("El archivo no existe.")
except PermissionError:
print("No tienes permisos para leer el archivo.")
else:
print("Archivo leído correctamente.")
print(contenido)
finally:
if archivo:
archivo.close()
print("Operación finalizada.")
El bloque finally es ideal para liberar recursos: cerrar archivos, conexiones a bases de datos o redes. Se ejecuta sin importar qué, incluso si hay un return dentro del bloque try.
En la práctica moderna, el gestor de contexto with reemplaza este patrón de forma más elegante:
try:
with open("datos.txt", "r") as archivo:
contenido = archivo.read()
except FileNotFoundError:
print("El archivo no existe.")
else:
print(contenido)
Con with, el archivo se cierra automáticamente al salir del bloque, aunque ocurra un error.
5. Las excepciones más comunes en Python
Conocer los nombres de las excepciones más frecuentes te ahorra tiempo al depurar:
| Excepción | Cuándo ocurre | Ejemplo |
|---|---|---|
ValueError | Tipo correcto pero valor inadecuado | int("hola") |
TypeError | Operación sobre un tipo incorrecto | "2" + 2 |
ZeroDivisionError | División entre cero | 10 / 0 |
IndexError | Índice fuera del rango de una lista | [1, 2][5] |
KeyError | Clave inexistente en un diccionario | {"a": 1}["b"] |
AttributeError | Atributo o método que no existe | (42).upper() |
FileNotFoundError | Archivo que no existe | open("x.txt") |
ImportError | Módulo que no se puede importar | import modulo_falso |
NameError | Variable no definida | print(variable_inexistente) |
RecursionError | Recursión demasiado profunda | función que se llama a sí misma sin fin |
Todas estas excepciones heredan de la clase base Exception, que a su vez hereda de BaseException. Por eso capturar Exception atrapa casi todos los errores comunes.
6. Lanzar errores con raise
A veces tú mismo necesitas lanzar una excepción cuando detectas una condición inválida en tu código. Para eso usas raise.
def calcular_raiz(numero):
if numero < 0:
raise ValueError(f"No se puede calcular la raíz de un número negativo: {numero}")
return numero ** 0.5
try:
print(calcular_raiz(-4))
except ValueError as e:
print(f"Error: {e}")
# Error: No se puede calcular la raíz de un número negativo: -4
También puedes relanzar una excepción que ya capturaste, usando raise sin argumentos dentro de un bloque except. Esto es útil cuando quieres registrar el error pero no suprimirlo:
try:
resultado = 10 / 0
except ZeroDivisionError as e:
print(f"Registrando error: {e}")
raise # relanza la misma excepción
Y puedes encadenar excepciones para indicar que un error fue causado por otro:
try:
int("abc")
except ValueError as e:
raise RuntimeError("Fallo al procesar entrada") from e
7. Crear tus propias excepciones
Para proyectos medianos o grandes, es una buena práctica crear excepciones propias que describan los errores específicos de tu aplicación. Solo tienes que crear una clase que herede de Exception.
class SaldoInsuficienteError(Exception):
def __init__(self, saldo, monto):
self.saldo = saldo
self.monto = monto
super().__init__(f"Saldo insuficiente. Tienes {saldo}, intentas retirar {monto}.")
class CuentaBancaria:
def __init__(self, saldo_inicial):
self.saldo = saldo_inicial
def retirar(self, monto):
if monto > self.saldo:
raise SaldoInsuficienteError(self.saldo, monto)
self.saldo -= monto
return self.saldo
cuenta = CuentaBancaria(100)
try:
cuenta.retirar(200)
except SaldoInsuficienteError as e:
print(e)
print(f"Saldo actual: {e.saldo}")
# Saldo insuficiente. Tienes 100, intentas retirar 200.
# Saldo actual: 100
Las excepciones propias hacen tu código mucho más expresivo: quien lee el traceback entiende inmediatamente qué salió mal, sin necesidad de buscar en la lógica del programa.
8. Cómo depurar tu código en Python
El manejo de excepciones captura errores en producción, pero durante el desarrollo necesitas encontrar la causa raíz del problema. Aquí las técnicas más usadas.
Leer el traceback completo
El traceback se lee de abajo hacia arriba. La última línea indica el tipo de error y el mensaje. Las líneas de arriba muestran la cadena de llamadas que llevó al error, desde la más reciente hasta la más antigua.
Traceback (most recent call last):
File "app.py", line 12, in <module>
procesar(datos)
File "app.py", line 7, in procesar
return calcular(x)
File "app.py", line 3, in calcular
return 10 / x
ZeroDivisionError: division by zero
El error real ocurrió en la línea 3, dentro de calcular(). Las demás líneas son el camino que siguió el programa hasta llegar ahí.
print() para inspeccionar valores
La forma más rápida de depurar. Añade prints estratégicos para ver el estado de tus variables:
def procesar_lista(items):
print(f"[DEBUG] items recibidos: {items}")
for i, item in enumerate(items):
print(f"[DEBUG] procesando índice {i}: {item}")
resultado = item * 2
return resultado
El módulo logging
Para proyectos reales, print() no es suficiente. El módulo logging te permite controlar el nivel de detalle, guardar mensajes en archivos y desactivarlos en producción sin borrar el código.
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(levelname)s - %(message)s"
)
def dividir(a, b):
logging.debug(f"Intentando dividir {a} / {b}")
try:
resultado = a / b
logging.info(f"Resultado: {resultado}")
return resultado
except ZeroDivisionError:
logging.error("División por cero detectada")
return None
dividir(10, 2)
dividir(10, 0)
Los niveles de logging en orden de gravedad son: DEBUG, INFO, WARNING, ERROR y CRITICAL. En producción sueles dejar solo WARNING o superior para no saturar los registros.
El depurador integrado: pdb
Python incluye un depurador interactivo llamado pdb. Te permite pausar la ejecución y explorar el estado del programa línea a línea.
import pdb
def calcular(x, y):
pdb.set_trace() # la ejecución se pausa aquí
resultado = x + y
return resultado
calcular(3, 5)
Una vez pausado, los comandos más útiles son:
n— ejecuta la siguiente línea (next)s— entra dentro de una función (step)c— continúa hasta el siguiente breakpoint (continue)p variable— imprime el valor de una variableq— sale del depurador
Desde Python 3.7 puedes usar breakpoint() en lugar de import pdb; pdb.set_trace(), que es más corto y configurable.
def calcular(x, y):
breakpoint() # equivalente moderno
return x + y
Depurar con el IDE
VS Code y PyCharm incluyen depuradores visuales donde puedes hacer clic en el margen para poner un breakpoint, ver todas las variables en un panel y avanzar línea a línea sin escribir ningún comando. Para proyectos medianos o grandes, es el método más cómodo.
9. Buenas prácticas en el manejo de errores
Sé específico al capturar excepciones. Capturar Exception genérico oculta errores que no esperabas. Siempre que puedas, especifica el tipo exacto.
# Mal: captura todo, incluyendo errores de programación
try:
procesar()
except Exception:
pass
# Bien: solo captura lo que sabes manejar
try:
procesar()
except ValueError as e:
print(f"Dato inválido: {e}")
Nunca uses un except vacío o con solo pass a menos que tengas una razón muy clara. Suprimir errores silenciosamente es una fuente clásica de bugs difíciles de encontrar.
Registra los errores inesperados en lugar de ocultarlos. Si capturas una excepción genérica para evitar que el programa se caiga, al menos guarda el error en el log:
import logging
try:
operacion_critica()
except Exception as e:
logging.exception("Error inesperado en operacion_critica")
# logging.exception() incluye el traceback completo automáticamente
Usa finally o gestores de contexto para liberar recursos. Los archivos, conexiones a bases de datos y sockets deben cerrarse siempre, aunque ocurra un error.
Coloca el mínimo de código posible dentro del try. Mientras más código haya en el bloque try, más difícil es saber qué línea causó el error. Limita el bloque al código que realmente puede lanzar la excepción que quieres capturar.
# Mal: demasiado código en el try
try:
datos = obtener_datos()
datos_limpios = limpiar(datos)
resultado = calcular(datos_limpios)
guardar(resultado)
except Exception:
print("Algo falló")
# Bien: try enfocado
datos = obtener_datos()
datos_limpios = limpiar(datos)
try:
resultado = calcular(datos_limpios)
except ValueError as e:
print(f"Error en el cálculo: {e}")
return
guardar(resultado)
10. Resumen final
Lo que debes recordar:
- Una excepción es un error en tiempo de ejecución. Aprender a leer el traceback te dice exactamente dónde y por qué falló el programa.
try/exceptes la estructura básica para capturar y manejar errores sin que el programa se rompa.elsese ejecuta solo si no hubo error.finallyse ejecuta siempre, ideal para liberar recursos.- Sé específico: captura la excepción exacta que esperas, no todo con
Exception. - Usa
raisepara lanzar errores cuando detectas condiciones inválidas en tu propio código. - Crea excepciones propias para proyectos con lógica de negocio compleja: hacen el código más expresivo.
- Para depurar, lee el traceback de abajo hacia arriba, usa
loggingen lugar deprint()en proyectos reales, y aprovechabreakpoint()cuando necesitas explorar el estado del programa.
El manejo de errores no es solo una técnica defensiva: es parte del diseño de tu programa. Un código que gestiona bien los fallos es más confiable, más fácil de mantener y mucho más fácil de depurar cuando algo inesperado ocurre en producción.
No hay comentarios todavía. Sé el primero en compartir tu opinión.