Excepciones en Python: try, except, else, finally, raise y excepciones propias
Una excepción en Python es un objeto que señala un error e interrumpe la ejecución normal del código hasta que lo captura un bloque try/except; else se ejecuta si no hubo error y finally siempre. Un error se puede provocar con la instrucción raise, por ejemplo raise ValueError("importe negativo"), y una excepción propia se crea con una clase que hereda de Exception.
Una excepción en Python es un objeto que indica que se ha producido un error y que interrumpe el curso normal del programa. Si nadie la captura, Python muestra un traceback y termina. Las excepciones se capturan con la construcción try/except y se provocan con la instrucción raise. Veamos cómo funciona todo esto, qué errores son los más frecuentes y cómo no convertir el manejo de errores en un modo de ocultarlos.
Qué es una excepción en Python
Cuando una operación no puede completarse —una división entre cero, el acceso a una clave que no existe, la conversión de una palabra a número—, Python crea un objeto de excepción y lo «lanza». La ejecución de la función actual se detiene, la excepción sube por la cadena de llamadas hasta encontrar un manejador adecuado. Si no lo encuentra, el programa se cae.
>>> 10 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Y respondiendo a una pregunta frecuente: al dividir entre cero en Python se produce la excepción ZeroDivisionError. Esto ocurre tanto con / como con // y con %.
Cómo leer un traceback
El traceback es el informe de cómo llegó el programa hasta el error. Es más cómodo leerlo de abajo arriba. La última línea es lo principal: el tipo de excepción y el mensaje. Más arriba está la línea de código donde ocurrió todo, y más arriba aún, quién la llamó, hasta llegar al punto de entrada.
Traceback (most recent call last):
File "gastos.py", line 9, in <module>
total = leer_importe(fila)
File "gastos.py", line 4, in leer_importe
return float(bruto.strip())
~~~~~^^^^^^^^^^^^^
ValueError: could not convert string to float: 'cien'
Desde Python 3.11, bajo la línea de código se dibujan marcas ~~~^^^ (PEP 657) que muestran qué expresión exacta de la línea falló. Esto ayuda mucho cuando en una misma línea hay varias llamadas seguidas.
Sugerencias «Did you mean»
Desde Python 3.10, si se escribe mal un nombre, el error propone la variante más cercana. En Python 3.15 las sugerencias son más inteligentes: el intérprete conoce los hábitos de otros lenguajes. Llamar a list.push sugerirá append, y "café".toUpperCase() sugerirá el método upper. La sugerencia también mira campos anidados: si un diccionario está en el atributo totals de un objeto y se llama a items() sobre el objeto mismo, el error propondrá .totals.items. Además, delattr() también sugiere nombres parecidos.
Tipos de excepciones en Python
Hay decenas de excepciones integradas, pero al empezar verá sobre todo estas:
| Excepción | Cuándo aparece | Ejemplo |
|---|---|---|
NameError | El nombre no está definido, casi siempre por una errata | print(totl) |
TypeError | La operación no sirve para ese tipo | "5" + 3 |
ValueError | El tipo es correcto, pero el valor no | int("cien") |
KeyError | La clave no existe en el diccionario | d["inexistente"] |
IndexError | El índice está fuera de la lista | [1, 2][5] |
AttributeError | El objeto no tiene ese método o campo | [].push(1) |
ZeroDivisionError | División entre cero | 1 / 0 |
FileNotFoundError | El archivo no existe | open("no_existe.txt") |
Aparte está SyntaxError (y su caso particular IndentationError): se produce ya al analizar el archivo, antes de ejecutar el código, por lo que no se puede capturar con try en el mismo archivo.
Jerarquía de excepciones
Las excepciones son clases y están organizadas en una jerarquía. En la cima está BaseException. De ella heredan Exception (casi todos los errores «normales») y algunas excepciones del sistema: KeyboardInterrupt, SystemExit, GeneratorExit.
BaseException
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── KeyError
│ └── IndexError
├── OSError
│ └── FileNotFoundError
├── TypeError
└── ValueError
La jerarquía es importante al capturar: except LookupError captura tanto KeyError como IndexError. Y KeyboardInterrupt no es un error del código: así avisa Python de que el usuario ha pulsado Ctrl+C. No hereda de Exception a propósito, para que un except Exception no impida interrumpir el programa.
Gestión de excepciones: try/except/else/finally
El código que puede fallar se coloca en el bloque try. En el bloque except se indica el tipo de error y qué hacer con él. Precisamente except es el bloque que se usa en Python para el manejo de excepciones.
def leer_importe(bruto):
try:
valor = float(bruto)
except ValueError as e:
print(f"Omito la línea: {e}")
return None
else:
return valor # se ejecuta solo si no hubo error
finally:
print("análisis completado") # se ejecuta siempre
print(leer_importe("250"))
En este ejemplo la salida empieza por «análisis completado» y después aparece 250.0. El return del bloque else guarda el valor; después Python ejecuta obligatoriamente finally y solo entonces devuelve el resultado. Por eso finally es fiable para la limpieza: cerrar un archivo, liberar una conexión.
Si en finally se escribe un return propio, sustituye al anterior e incluso «traga» la excepción. Desde Python 3.14 este código genera un SyntaxWarning (PEP 765): no escriba return en finally.
Varias excepciones a la vez
Se pueden escribir varios bloques except para tipos distintos, o enumerar los tipos en una tupla:
try:
importe = int(datos["importe"])
except KeyError:
importe = 0
except (ValueError, TypeError) as e:
print("Importe incorrecto:", e)
importe = 0
Los bloques se comprueban de arriba abajo y se ejecuta el primero que coincide, así que los tipos más concretos van antes que los generales. Desde Python 3.14 (PEP 758), los tipos se pueden enumerar sin paréntesis —except ValueError, TypeError:—, pero si se usa as, los paréntesis siguen siendo obligatorios.
Cómo mostrar el texto del error y conocer su tipo
El objeto de excepción está disponible mediante as e. str(e) da el mensaje y type(e).__name__ da el nombre del tipo:
try:
int("cien")
except ValueError as e:
print(type(e).__name__) # ValueError
print(e) # invalid literal for int() with base 10: 'cien'
Módulo traceback: el texto completo del error
A veces hay que capturar el error pero conservar el traceback completo, para un registro o un informe. Para eso existe el módulo traceback:
import traceback
try:
1 / 0
except ZeroDivisionError:
traceback.print_exc() # imprime el traceback en stderr
texto = traceback.format_exc() # el mismo texto como cadena
Si escribe registros con el módulo logging, es más sencillo llamar a logging.exception("no se pudo calcular") dentro de except: el traceback se añade automáticamente al mensaje.
Python raise: cómo provocar una excepción
La instrucción raise lanza una excepción manualmente. Así una función indica que no puede trabajar con esos datos:
def añadir_gasto(importe):
if importe < 0:
raise ValueError(f"El importe no puede ser negativo: {importe}")
...
añadir_gasto(-5)
# ValueError: El importe no puede ser negativo: -5
raise de Python es una instrucción, no una función, así que tras la palabra raise no van paréntesis adicionales: dentro de los paréntesis se pasan los argumentos del constructor de la excepción. Elija el tipo según el significado: un valor incorrecto es ValueError, un tipo incorrecto es TypeError.
raise sin argumentos y raise from
Dentro de un except, un raise sin nada reenvía la excepción actual sin cambios. Es útil cuando se quiere registrar algo, pero dejar el tratamiento al código que llama.
Si se sustituye una excepción por otra, use raise ... from e: el error original se conserva en la cadena y en el traceback se ven ambos:
def cargar_configuracion(ruta):
try:
with open(ruta) as f:
return f.read()
except FileNotFoundError as e:
raise RuntimeError(f"No hay configuración en {ruta}") from e
add_note: añadir contexto al error
Desde Python 3.11 (PEP 678), a una excepción se le puede añadir una nota con el método add_note() y volver a lanzarla. El tipo del error no cambia, y la nota aparece bajo el mensaje:
def analizar(bruto, num_linea):
try:
return float(bruto)
except ValueError as e:
e.add_note(f"archivo gastos.csv, línea {num_linea}")
raise
analizar("cien", 17)
# ValueError: could not convert string to float: 'cien'
# archivo gastos.csv, línea 17
Cómo crear una excepción propia en Python
Las excepciones propias sirven para los errores del dominio: presupuesto superado, pedido ya pagado, usuario bloqueado. Basta con una clase que herede de Exception:
class PresupuestoSuperado(Exception):
def __init__(self, categoria, limite, gastado):
super().__init__(f"{categoria}: límite {limite}, gastado {gastado}")
self.categoria = categoria
self.limite = limite
self.gastado = gastado
def comprobar_presupuesto(categoria, limite, gastado):
if gastado > limite:
raise PresupuestoSuperado(categoria, limite, gastado)
try:
comprobar_presupuesto("Comida", 10000, 12500)
except PresupuestoSuperado as e:
print("Aviso:", e) # Aviso: Comida: límite 10000, gastado 12500
Así el código que llama captura exactamente el caso de presupuesto superado y no lo confunde con erratas. En un proyecto grande conviene crear una clase base común (class ErrorRastreador(Exception)) y heredar de ella todas las excepciones propias; así se pueden capturar con un único except ErrorRastreador. Herede de Exception, no de BaseException.
Grupos de excepciones y except*
A veces hay varios errores a la vez, por ejemplo al validar un archivo entero o en tareas paralelas de asyncio.TaskGroup. Desde Python 3.11 se pueden agrupar en un ExceptionGroup y tratarlos por tipo con except*:
def validar_todo(filas):
errores = []
for f in filas:
try:
float(f)
except ValueError as e:
errores.append(e)
if errores:
raise ExceptionGroup("errores en el archivo", errores)
try:
validar_todo(["1", "a", "b"])
except* ValueError as grupo:
print(f"Líneas incorrectas: {len(grupo.exceptions)}") # Líneas incorrectas: 2
try except de todos los errores: por qué no conviene
El hábito más perjudicial es capturarlo todo: except: pass. El programa «deja de caerse», pero en realidad deja de avisar de los problemas. Un except desnudo captura incluso KeyboardInterrupt y SystemExit, así que el programa no se puede detener con Ctrl+C. except Exception: pass es algo mejor, pero también traga en silencio las erratas en los nombres y los errores de lógica.
Para gestionar excepciones correctamente en Python:
- capture solo el tipo que sabe tratar:
except ValueError, no todo; - mantenga el bloque
trycorto: solo las líneas que pueden fallar de esa forma; - si captura de forma amplia (
except Exception) en el nivel superior del programa, registre siempre el traceback; - no use excepciones para ramificar el flujo cuando basta una comprobación: en casos simples,
dict.get()en lugar detry/except KeyError; - para los recursos prefiera
with: cierra el archivo aunque haya un error.
Resumen
Las excepciones en Python son el mecanismo normal para señalar errores, no un motivo para ocultarlos. Lea el traceback de abajo arriba, capture tipos concretos con try/except, use else para el código de «todo ha ido bien» y finally para la limpieza. Con raise se señala un error, raise from y add_note conservan el contexto, y las excepciones propias heredan de Exception. El curso de Python trabaja este tema en la práctica: el registro de gastos deja de caerse por una línea de CSV incorrecta y expresa el presupuesto superado con su propia excepción.
Preguntas frecuentes
¿En qué se diferencia un error de una excepción en Python?
En el lenguaje cotidiano son sinónimos. Técnicamente, SyntaxError se detecta antes, al analizar el código, y el resto de errores aparecen durante la ejecución como objetos de excepción que se pueden capturar.
¿Se puede continuar la ejecución desde el mismo punto tras una excepción?
No. Tras capturarla, la ejecución continúa después del bloque try/except, no en la línea donde se produjo el error. Si hay que tratar cada línea de un archivo, coloque el try dentro del bucle.
¿Qué pasa si la excepción se produce dentro de un except?
Sube hacia arriba, y la excepción original se guarda en su atributo __context__. En el traceback aparecerá la frase During handling of the above exception, another exception occurred.
¿Cómo comprobar en las pruebas que una función lanza una excepción?
En pytest se usa with pytest.raises(ValueError):, y en unittest, self.assertRaises(ValueError). La prueba solo pasa si dentro del bloque se produce una excepción del tipo indicado.
¿Qué es assert y en qué se diferencia de raise?
assert condición, "mensaje" lanza AssertionError si la condición es falsa. Es una herramienta de comprobación durante el desarrollo: al ejecutar con la opción -O los assert se desactivan, así que la validación de los datos del usuario debe hacerse con if y raise.
Este tema se explica a fondo y con práctica en el curso «Python 3.15: от новичка до профи».
Ir al curso