except específico o genérico: el tipo de excepción decide qué Python puede recuperar

La decisión difícil no es si usar try/except, sino qué error estás dispuesto a tratar. Un bloque que captura cualquier excepción puede hacer que el programa parezca tranquilo mientras oculta un fallo que todavía no sabes resolver. En cambio, una captura específica obliga a responder una pregunta más incómoda: ¿qué situación normal o prevista puede recuperar este código y qué situaciones deben seguir subiendo para que alguien las vea?

Python distingue entre un error de sintaxis y una excepción. El primero impide que el intérprete analice correctamente el programa; la segunda aparece durante la ejecución de una instrucción que sí era válida. La documentación oficial de Python sobre errores y excepciones muestra que las excepciones tienen tipos, contexto y reglas de propagación. Esa información no es un detalle académico: decide si tu programa pide de nuevo un dato, libera un recurso, registra un problema o continúa con un estado incorrecto.

Dos programas reciben el mismo error, pero no toman la misma decisión

Supón que un programa pide un número:

edad = int(input("Edad: "))

Si el usuario escribe veinte, Python lanza ValueError. Esa situación puede formar parte del flujo normal: el programa puede informar del formato y pedir otro valor. Pero si el archivo del programa contiene una variable mal escrita y provoca NameError, repetir la pregunta no arregla nada. Ambos son excepciones, pero no son el mismo tipo de problema.

EstrategiaResultado con entrada inválidaResultado con un defecto inesperado
except ValueErrorPermite mostrar una instrucción y recuperar la entrada.Deja que otro error sea visible para corregirlo.
except ExceptionTambién puede recuperar la entrada.Puede ocultar un bug como si fuera un problema normal del usuario.

El segundo bloque no es automáticamente incorrecto. Puede ser adecuado en una frontera de la aplicación donde necesitas registrar cualquier fallo y devolver una respuesta controlada. El problema aparece cuando se usa como sustituto de entender qué puede salir mal. Capturar de forma amplia es una decisión de arquitectura; para un principiante que todavía está explorando la causa, suele ser una venda.

Capturar el tipo correcto conserva información

La jerarquía de excepciones de Python no existe solo para que los nombres sean más precisos. Permite tratar grupos relacionados cuando tiene sentido y distinguir situaciones que requieren recuperaciones diferentes. La referencia de excepciones incorporadas de Python documenta, entre otras, TypeError, ValueError, OSError, KeyError y IndexError.

Considera una función que lee una configuración:

def cargar_puerto(ruta):
    try:
        with open(ruta, encoding="utf-8") as archivo:
            texto = archivo.read().strip()
        return int(texto)
    except OSError:
        raise RuntimeError("No se pudo leer la configuración")
    except ValueError:
        raise RuntimeError("El puerto no es un número válido")

Las dos excepciones terminan convertidas en un error de la aplicación, pero cada mensaje conserva una causa distinta. Un archivo ausente se investiga en el sistema de archivos; un contenido inválido se corrige en la configuración. Si utilizas un except Exception único, tendrás que reconstruir esa diferencia desde un log demasiado genérico.

La captura específica tampoco significa listar todos los tipos imaginables. Empieza por los errores que sabes tratar. Si una excepción desconocida aparece, deja que se propague durante el desarrollo. El traceback puede parecer intimidante, pero contiene el lugar y la cadena de llamadas que necesitas para corregir el defecto.

El experimento que revela el problema de except genérico

Prueba estas dos versiones en un archivo pequeño. La primera es silenciosa:

def convertir(texto):
    try:
        return int(texto)
    except Exception:
        return 0

print(convertir("12"))
print(convertir("doce"))

El resultado parece cómodo: los dos casos producen un número. Ahora introduce un error de programación deliberado dentro del bloque:

def convertir(texto):
    try:
        numero = int(texto)
        return numero + limite_no_definido
    except Exception:
        return 0

El mismo valor de retorno oculta dos historias completamente distintas: una entrada que el usuario debe corregir y una variable que el programador olvidó definir. El programa no se cae, pero tampoco cumple la operación. Esa tranquilidad falsa es más difícil de diagnosticar que un fallo visible.

Con una captura específica, el defecto se mantiene visible:

def convertir(texto):
    try:
        numero = int(texto)
        return numero + limite_no_definido
    except ValueError:
        return 0

Ahora la entrada “doce” puede recibir una recuperación, mientras que NameError llega al traceback. No has eliminado errores: has separado una condición esperada de una condición que todavía necesita investigación.

else separa el éxito de la operación protegida

Muchos principiantes colocan todo dentro de try porque parece cómodo:

try:
    datos = cargar_datos()
    procesar_datos(datos)
    guardar_resultado(datos)
except ValueError:
    mostrar_error_de_formato()

El problema es que el except ValueError puede capturar un ValueError producido por procesar_datos() o guardar_resultado(), aunque solo pretendías manejar el formato de cargar_datos(). El bloque protegido se volvió demasiado grande.

El else permite declarar qué debe ocurrir solo si el try terminó sin excepción:

try:
    datos = cargar_datos()
except ValueError:
    mostrar_error_de_formato()
else:
    procesar_datos(datos)
    guardar_resultado(datos)

La referencia del bloque try de Python explica que el código del else no queda accidentalmente dentro del área cuya excepción estás capturando. La diferencia es pequeña en sintaxis, pero grande en intención: el try cubre la operación que sabes cómo recuperar; el else expresa el camino exitoso que sigue después.

finally no decide si la operación fue correcta; garantiza la limpieza

El finally responde a otra pregunta: ¿qué acción debe ejecutarse tanto si hubo excepción como si no? Cerrar un archivo, liberar una conexión o quitar un bloqueo son ejemplos de limpieza.

recurso = adquirir_recurso()
try:
    usar(recurso)
finally:
    liberar(recurso)

El bloque se ejecuta aunque usar() lance una excepción. Esto no significa que el error haya sido resuelto. Significa que el programa intenta dejar el sistema en un estado seguro antes de continuar la propagación. Para archivos, el administrador de contexto suele ser más claro:

with open("datos.txt", encoding="utf-8") as archivo:
    contenido = archivo.read()

Python documenta with como una forma de asegurar acciones de limpieza predefinidas. Si trabajas con un recurso que tiene un protocolo de contexto, úsalo antes de construir manualmente un try/finally. La idea importante es que manejo del error y liberación del recurso son responsabilidades distintas.

Ten cuidado con un return dentro de finally. Puede reemplazar el valor que intentabas devolver o impedir que una excepción se propague como esperabas. La limpieza debe limpiar; no debería reescribir silenciosamente el resultado de la función.

raise permite trasladar un problema sin borrar su significado

Capturar una excepción no te obliga a consumirla. Puedes añadir contexto y volver a lanzarla:

def guardar_usuario(usuario):
    try:
        insertar_en_base_de_datos(usuario)
    except OSError as error:
        raise RuntimeError("No se pudo guardar el usuario") from error

La cláusula from conserva la relación entre el error de la capa baja y el error que entiende la capa de aplicación. La persona que lee el traceback ve que el problema de alto nivel tiene una causa anterior. Sin esa relación, un mensaje nuevo puede ocultar el origen.

También puedes usar raise sin argumentos dentro de un except para volver a lanzar la excepción actual después de registrarla:

try:
    procesar_pago()
except Exception:
    registrar_error()
    raise

Este patrón es distinto de devolver un valor comodín. El registro añade observabilidad; la propagación impide declarar éxito cuando no lo hubo.

Una matriz sencilla para decidir qué hacer

Antes de escribir el bloque, clasifica la situación con dos ejes: ¿es esperada en el flujo? y ¿tienes una recuperación clara? La siguiente matriz no sustituye el diseño, pero ayuda a evitar un except reflejo.

SituaciónAcción recomendadaEjemplo
Esperada y recuperableCapturar un tipo específico, informar o repetir.ValueError al convertir una entrada del usuario.
Esperada, pero no recuperable aquíAñadir contexto y propagar a una capa que decida.OSError al leer una configuración requerida.
Inesperada y desconocidaDejar visible el traceback durante el desarrollo; registrar y propagar en producción.NameError por un defecto de código.
Recurso que debe liberarseUsar with o finally independientemente del éxito.Archivo, conexión o bloqueo.

La pregunta “¿qué hago si algo falla?” es demasiado amplia. Reemplázala por “¿qué clase de fallo puedo reconocer y cuál es la recuperación correcta?”. Esa pregunta produce bloques más pequeños y mensajes más honestos.

Excepciones personalizadas: nombrar el problema del dominio

Crear una excepción personalizada no hace que un programa sea más profesional por sí mismo. Tiene sentido cuando el dominio posee una condición que merece un nombre estable y una respuesta diferenciada. En una aplicación de reservas, AsientoNoDisponibleError comunica más que un ValueError genérico si la capa superior debe ofrecer otras opciones al usuario.

class AsientoNoDisponibleError(Exception):
    pass


def reservar(asiento, ocupados):
    if asiento in ocupados:
        raise AsientoNoDisponibleError(asiento)
    ocupados.add(asiento)

La clase no necesita una jerarquía enorme ni decenas de atributos. Su valor está en permitir que otro código capture exactamente esa condición:

try:
    reservar("A12", ocupados)
except AsientoNoDisponibleError:
    mostrar_alternativas()

Si ningún consumidor necesita distinguirla, un tipo incorporado puede ser suficiente. El nombre debe representar una decisión del dominio, no una capa ornamental encima de cada línea que puede fallar.

Qué significa que un programa sea más robusto

Un programa robusto no es el que nunca muestra un traceback. Es el que distingue entradas inválidas de defectos internos, limpia recursos, conserva contexto y falla de una manera que permite actuar. A veces la respuesta correcta es seguir; a veces es pedir el dato otra vez; a veces es detenerse y alertar al desarrollador.

Si quieres reforzar esta forma de pensar, compárala con la guía sobre cómo leer un traceback de Python, con el artículo sobre leer y escribir archivos y con la explicación de código Python limpio. La captura de excepciones es una decisión dentro de un programa, no una capa que se añade al final para que deje de quejarse.

Preguntas frecuentes sobre try/except

¿Es malo usar except Exception?

No siempre. Puede ser útil en una frontera de la aplicación para registrar un fallo y devolver una respuesta controlada. Es peligroso cuando se usa dentro de cualquier función sin una recuperación clara, porque puede ocultar defectos y confundirlos con entradas esperadas.

¿Qué diferencia práctica hay entre else y poner más código en try?

El código del else se ejecuta solo si el try terminó sin excepción y queda fuera del área cuya excepción pretendes capturar. Eso evita manejar accidentalmente un error producido por una operación posterior.

¿finally siempre se ejecuta?

Se ejecuta al salir del bloque, incluso cuando hay una excepción o un retorno en el try. Debe usarse para limpieza y conviene evitar retornos dentro de finally, porque pueden ocultar excepciones o reemplazar valores.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio