# Excepciones en Python: try, except, else, finally, raise y excepciones propias

URL: https://skillaria.top/es/guides/python/excepciones-python

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 "", line 1, in
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
    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 `try` corto: 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 de `try/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.

## FAQ

**¿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.


---
Source: Skillaria.Top (https://skillaria.top)
