Errores y excepciones

En Python las excepciones no son excepcionales: se usan para el flujo normal mucho más que en otros lenguajes. Esta lección va de usarlas bien, del with, y del except vacío, que es la peor costumbre que se puede coger.

try, except, else, finally

def convertir(s):
    try:
        n = int(s)
    except ValueError:
        return "no es un número"
    except TypeError as e:
        return f"ni siquiera es texto: {e}"
    else:
        return f"vale {n}"       # solo si NO hubo excepción
    finally:
        ...                      # siempre, incluso tras el return
salida'7'      -> vale 7
'x'      -> no es un número
None     -> ni siquiera es texto: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
'  8  '  -> vale 8

Las cuatro partes, y para qué sirve cada una:

Bloque Cuándo se ejecuta
try siempre; es lo que puede fallar
except si saltó esa excepción
else si no saltó ninguna
finally siempre, haya error o no, incluso tras un return

El else parece prescindible y no lo es: deja en el try solo la línea que puede fallar. Si metes ahí todo el trabajo, acabas capturando por accidente una excepción que venía de otro sitio.

Varios tipos de golpe

except (KeyError, IndexError) as e:      # con "as", con paréntesis
    ...

except KeyError, IndexError:             # sin "as": desde Python 3.14, sin paréntesis
    ...
salidacon 'as', con paréntesis: KeyError 'x'
sin 'as', ya no hacen falta los paréntesis
Ese segundo caso es nuevo y conviene leer la letra pequeña. Python 3.14 permite omitir los paréntesis, pero solo si no usas as. Lo comprobé: con as sigue dando «multiple exception types must be parenthesized when using ‘as’». Y en 3.13 la forma sin paréntesis no compila en ningún caso, así que no la uses si tu código tiene que correr en versiones anteriores.

Excepciones propias, con datos dentro

class SaldoInsuficiente(Exception):
    def __init__(self, faltan):
        super().__init__(f"faltan {faltan} €")
        self.faltan = faltan
salidafaltan 30 €  -> puedo decidir con e.faltan = 30

Eso es lo que distingue una excepción útil de un mensaje de texto: quien la recibe puede decidir con ella, no solo imprimirla. Y heredar de Exception, nunca de BaseException, que está reservada para cosas como el Ctrl+C.

Relanzar sin perder la causa

try:
    int("x")
except ValueError as e:
    raise RuntimeError("configuración inválida") from e
salidaconfiguración inválida  <- causada por ValueError: invalid literal for int() with base 10: 'x'

Ese from e es lo que hace que en la traza aparezca el «The above exception was the direct cause of…». Sin él, la pista original se pierde y quien depure el problema solo verá tu mensaje genérico.

with: cerrar sin acordarse

with p.open(encoding="utf-8") as f:
    print(f.readline())
# aquí ya está cerrado, pase lo que pase
salidaprimera línea: 'una línea'
¿cerrado al salir del with? True

Todo lo que haya que cerrar o liberar —archivos, conexiones, cerrojos— se usa así. Es la versión corta y correcta de un try/finally, y además funciona si la excepción salta a mitad.

Uno propio, en cinco líneas

from contextlib import contextmanager
import time

@contextmanager
def cronometro(nombre):
    t0 = time.perf_counter()
    try:
        yield
    finally:
        print(f"{nombre}: {(time.perf_counter()-t0)*1000:.1f} ms")

with cronometro("sumar un millón"):
    sum(range(1_000_000))
salidasumar un millón: 18.7 ms

Lo que va antes del yield se ejecuta al entrar, y lo de después al salir. El try/finally alrededor del yield no es opcional: sin él, una excepción dentro del with se saltaría la parte de limpieza.

Lo que no hay que hacer

try:
    algo_que_puede_fallar()
except Exception:
    pass            # el error desaparece y nadie se entera
Un except vacío no arregla el problema, lo esconde hasta que reaparece más lejos y más raro. Tres reglas que evitan casi todos los disgustos: captura el tipo concreto, no Exception a secas; si no vas a hacer nada con el error, déjalo subir; y no uses excepciones para el flujo normal cuando hay una comprobación directa —d.get(k) antes que un try alrededor de d[k].

Dicho eso, en Python sí es idiomático «pedir perdón en vez de permiso»: intentar la operación y capturar si falla, en lugar de comprobar antes. Entre archivos y red, comprobar antes no sirve de mucho, porque las cosas cambian entre la comprobación y el uso.

Situación Qué se hace
Puede fallar y sabes arreglarlo except TipoConcreto
Necesitas el objeto del error except X as e
Algo tiene que cerrarse with
Relanzar con otro tipo raise Otro(...) from e
Un error propio del dominio una clase que herede de Exception
No hay nada que hacer dejarlo subir, nunca pass

Todo el código de esta lección se ejecutó con Python 3.14 en un contenedor limpio antes de publicarla; las salidas y los errores están copiados de esa ejecución.