Manejo de errores en Python: try, except, else, finally y excepciones propias

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 17 min de lectura

Todo programa que hace algo real — leer archivos, llamar a APIs, pedir datos al usuario — puede encontrarse con situaciones inesperadas. El archivo no existe. El servidor no responde. El usuario teclea texto donde se esperaba un número. Python tiene un sistema para manejar esas situaciones de forma controlada, sin que el programa reviente con un mensaje de error incomprensible para el usuario final.

Las excepciones no son el enemigo. Son mensajes de Python diciéndote exactamente qué salió mal y dónde. Aprender a capturarlas, interpretarlas y lanzarlas en el momento correcto es lo que separa un script frágil de un programa robusto.

🛡️ try / except: capturar errores con precisión

La estructura básica: el código que puede fallar va en el bloque try. Si ocurre una excepción, Python salta al bloque except correspondiente en lugar de detener el programa.

# Sin manejo de errores — el programa muere:
numero = int(input("Introduce un número: "))   # si el usuario escribe "hola" → ValueError

# Con manejo de errores:
try:
    numero = int(input("Introduce un número: "))
    print(f"El doble es {numero * 2}")
except ValueError:
    print("Eso no es un número válido.")

# El programa sigue ejecutándose después del except
print("Gracias por participar.")

La clave es capturar la excepción más específica posible. Si sabes que puede ser un ValueError, captura ValueError, no Exception a ciegas. Así no silencias errores que sí deberían propagarse.

# Acceder al objeto excepción con 'as e':
try:
    resultado = 10 / 0
except ZeroDivisionError as e:
    print(f"Error: {e}")         # Error: division by zero
    print(type(e).__name__)      # ZeroDivisionError

# Obtener información del error:
try:
    lista = [1, 2, 3]
    print(lista[10])
except IndexError as e:
    print(f"Índice inválido: {e}")
    # Índice inválido: list index out of range
Balón de fútbol sobre el césped de un estadio vacío a vista de suelo
El partido se detiene — como una excepción que interrumpe el flujo del programa. Sin try/except, el juego no se reanuda. Con él, sí. Fuente: Pexels (licencia libre).

⚡ Capturar múltiples excepciones

Puedes encadenar varios except, cada uno manejando un tipo distinto de error. Python los evalúa en orden y ejecuta el primero que coincida.

def dividir(a, b):
    try:
        resultado = a / b
        return resultado
    except ZeroDivisionError:
        print("No se puede dividir por cero.")
        return None
    except TypeError as e:
        print(f"Tipo incorrecto: {e}")
        return None

dividir(10, 2)      # → 5.0
dividir(10, 0)      # → "No se puede dividir por cero."
dividir(10, "dos")  # → "Tipo incorrecto: unsupported operand type(s)..."

# Capturar varios tipos en un solo except:
try:
    valor = int(input("Número: "))
    print(100 / valor)
except (ValueError, ZeroDivisionError) as e:
    print(f"Entrada inválida: {e}")

# Fallback genérico al final (con logging):
import logging
try:
    resultado = operacion_compleja()
except ValueError as e:
    print(f"Valor incorrecto: {e}")
except ConnectionError as e:
    print(f"Error de conexión: {e}")
except Exception as e:
    logging.error(f"Error inesperado: {e}", exc_info=True)
    raise    # re-lanzar para no silenciarlo
💡 Orden importa: Pon las excepciones más específicas antes que las genéricas. Si pones Exception primero, capturará todo y los except siguientes nunca se ejecutarán.

🔄 else y finally: completar el bloque

else se ejecuta solo si el bloque try terminó sin excepción. finally se ejecuta siempre, con o sin error. Son opcionales pero tienen un propósito claro.

try:
    numero = int(input("Número positivo: "))
    if numero <= 0:
        raise ValueError("Debe ser positivo")
    resultado = 100 / numero

except ValueError as e:
    print(f"Error de valor: {e}")

except ZeroDivisionError:
    print("División por cero.")

else:
    # Solo si try terminó sin excepciones:
    print(f"Resultado: {resultado}")
    print("Todo correcto.")

finally:
    # SIEMPRE, pase lo que pase:
    print("Bloque ejecutado (finally).")
Diagrama de flujo del bloque try except else finally en Python con ramas de decisión coloreadas
Diagrama de flujo del bloque try/except/else/finally: la rama roja va al except si hay error, la verde al else si no lo hay, y el morado (finally) siempre se ejecuta. Infografía: Ciberaula.

finally para liberar recursos

archivo = None
try:
    archivo = open("datos.txt", "r")
    contenido = archivo.read()
    procesar(contenido)
except FileNotFoundError:
    print("El archivo no existe.")
except PermissionError:
    print("Sin permisos de lectura.")
finally:
    if archivo:
        archivo.close()    # siempre se cierra, haya error o no

🚀 raise: lanzar excepciones intencionadamente

Usas raise cuando tu código detecta una condición que no puede manejar y quiere comunicársela a quien te llamó.

def calcular_imc(peso, altura):
    if altura <= 0:
        raise ValueError(f"La altura debe ser positiva, recibido: {altura}")
    if peso <= 0:
        raise ValueError(f"El peso debe ser positivo, recibido: {peso}")
    return round(peso / altura ** 2, 2)

# Quién llama puede manejar el error:
try:
    imc = calcular_imc(70, -1.75)
except ValueError as e:
    print(f"Datos inválidos: {e}")

# Re-lanzar después de logging:
try:
    resultado = operacion_critica()
except Exception as e:
    logging.error(f"Falló operación crítica: {e}")
    raise    # propaga la excepción original sin modificarla

# Lanzar una excepción diferente con contexto:
try:
    datos = json.loads(texto_recibido)
except json.JSONDecodeError as e:
    raise ValueError(f"El texto no es JSON válido: {texto_recibido[:50]}") from e

🏷️ Excepciones personalizadas

Crear tus propias excepciones hace el código más expresivo. Quien use tu módulo puede capturar exactamente el tipo de error que le interesa manejar.

# Hereda de Exception o de una subclase apropiada
class SaldoInsuficienteError(Exception):
    """Se lanza cuando el saldo no alcanza para la operación."""
    def __init__(self, saldo_actual, cantidad_requerida):
        self.saldo_actual = saldo_actual
        self.cantidad_requerida = cantidad_requerida
        super().__init__(
            f"Saldo insuficiente: tienes {saldo_actual}€, "
            f"necesitas {cantidad_requerida}€"
        )

class CuentaBloqueadaError(Exception):
    """Se lanza cuando la cuenta está bloqueada."""
    pass

class CuentaBancaria:
    def __init__(self, titular, saldo_inicial=0):
        self.titular = titular
        self._saldo = saldo_inicial
        self._bloqueada = False

    def retirar(self, cantidad):
        if self._bloqueada:
            raise CuentaBloqueadaError(f"Cuenta de {self.titular} bloqueada.")
        if cantidad > self._saldo:
            raise SaldoInsuficienteError(self._saldo, cantidad)
        self._saldo -= cantidad
        return self._saldo

# Uso:
cuenta = CuentaBancaria("Ana", 100)
try:
    cuenta.retirar(150)
except SaldoInsuficienteError as e:
    print(f"No se pudo retirar: {e}")
    print(f"  Faltan {e.cantidad_requerida - e.saldo_actual}€")
except CuentaBloqueadaError as e:
    print(f"Operación denegada: {e}")

🔒 with: el context manager como finally automático

El patrón with garantiza que los recursos se liberan correctamente sin necesidad de escribir finally explícito. Es la forma recomendada de trabajar con archivos, conexiones y cualquier recurso que deba cerrarse.

# Sin with (verboso y propenso a olvidar el cierre):
archivo = open("datos.txt")
try:
    contenido = archivo.read()
finally:
    archivo.close()

# Con with (equivalente, más limpio):
with open("datos.txt") as archivo:
    contenido = archivo.read()
# archivo.close() se llama automáticamente al salir del bloque

# Escritura segura:
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Primera línea\n")
    f.write("Segunda línea\n")
# Si hay error al escribir, el archivo se cierra de todas formas

# Múltiples recursos en un solo with (Python 3.10+):
with open("entrada.txt") as f_in, open("salida.txt", "w") as f_out:
    for linea in f_in:
        f_out.write(linea.upper())

# with en manejo de errores:
try:
    with open("datos.csv") as f:
        datos = f.readlines()
except FileNotFoundError:
    print("El archivo no existe.")
    datos = []
Referencia rápida de las excepciones built-in más habituales de Python con causa y ejemplo de código
Referencia rápida — Las excepciones built-in más habituales: qué las causa y un ejemplo de código que las lanza. Al final: raise para lanzar manualmente y cómo crear excepciones propias. Ficha: Ciberaula.

🐍 EAFP vs LBYL: el estilo Python de manejar errores

Python promueve el estilo EAFP (Easier to Ask Forgiveness than Permission): intenta la operación y captura el error si ocurre. El estilo contrario, LBYL (Look Before You Leap), comprueba antes de actuar. En Python el EAFP suele producir código más limpio.

diccionario = {"nombre": "Ana", "edad": 28}

# LBYL — comprobar antes:
if "email" in diccionario:
    email = diccionario["email"]
else:
    email = "sin email"

# EAFP — intentar y capturar:
try:
    email = diccionario["email"]
except KeyError:
    email = "sin email"

# EAFP aún más idiomático para este caso:
email = diccionario.get("email", "sin email")

# Otro ejemplo: abrir archivo
# LBYL:
import os
if os.path.exists("config.json"):
    with open("config.json") as f:
        config = json.load(f)

# EAFP:
try:
    with open("config.json") as f:
        config = json.load(f)
except FileNotFoundError:
    config = {}    # configuración por defecto

🛠️ Programa completo: lector de CSV robusto

import csv
import logging

logging.basicConfig(level=logging.WARNING,
                    format="%(levelname)s: %(message)s")

class ErrorCsvInvalido(ValueError):
    """CSV con estructura incorrecta o datos corruptos."""
    pass

def leer_empleados(ruta_csv):
    """
    Lee un CSV de empleados y devuelve una lista de dicts.
    Columnas esperadas: nombre, departamento, salario
    """
    empleados = []
    columnas_req = {"nombre", "departamento", "salario"}

    try:
        with open(ruta_csv, encoding="utf-8") as f:
            lector = csv.DictReader(f)

            # Verificar columnas
            if not columnas_req.issubset(set(lector.fieldnames or [])):
                faltantes = columnas_req - set(lector.fieldnames or [])
                raise ErrorCsvInvalido(
                    f"Columnas faltantes: {', '.join(faltantes)}"
                )

            for numero_fila, fila in enumerate(lector, start=2):
                try:
                    salario = float(fila["salario"])
                    if salario < 0:
                        raise ValueError("El salario no puede ser negativo")

                    empleados.append({
                        "nombre":       fila["nombre"].strip(),
                        "departamento": fila["departamento"].strip(),
                        "salario":      salario,
                    })
                except ValueError as e:
                    logging.warning(f"Fila {numero_fila} omitida: {e}")
                    continue    # salta filas con datos inválidos, no para todo

    except FileNotFoundError:
        print(f"Archivo no encontrado: {ruta_csv}")
        return []
    except PermissionError:
        print(f"Sin permisos para leer: {ruta_csv}")
        return []
    except ErrorCsvInvalido as e:
        print(f"Estructura CSV inválida: {e}")
        return []

    else:
        print(f"Leídos {len(empleados)} empleados correctamente.")

    return empleados

def resumen_por_departamento(empleados):
    """Calcula salario medio por departamento."""
    por_dept = {}
    for e in empleados:
        dept = e["departamento"]
        if dept not in por_dept:
            por_dept[dept] = []
        por_dept[dept].append(e["salario"])

    return {
        dept: round(sum(salarios) / len(salarios), 2)
        for dept, salarios in por_dept.items()
    }

# Uso
empleados = leer_empleados("empleados.csv")
if empleados:
    resumen = resumen_por_departamento(empleados)
    print("\nSalario medio por departamento:")
    for dept, media in sorted(resumen.items()):
        print(f"  {dept:<20} {media:>10.2f} €")

🐛 Errores clásicos con excepciones

1. Capturar Exception demasiado pronto y silenciar el error

# ❌ El error desaparece sin dejar rastro
try:
    resultado = calcular()
except Exception:
    pass    # ← nunca hagas esto

# ✅ Al menos registrar el error
except Exception as e:
    logging.error(f"Error en calcular(): {e}", exc_info=True)

2. Poner demasiado código en el try

# ❌ Difícil saber qué línea lanzó el error
try:
    datos = leer_archivo(ruta)
    datos = procesar(datos)
    guardar(datos)
    notificar_usuario()
except Exception as e:
    print(f"Algo falló: {e}")

# ✅ try acotado al código que puede fallar, con manejo específico
try:
    datos = leer_archivo(ruta)
except FileNotFoundError:
    print("Archivo no encontrado."); return

datos = procesar(datos)   # si esto falla, es un bug, que propague
guardar(datos)

3. Olvidar que finally no devuelve valor útil

# ❌ El return del finally sobreescribe el del try
def obtener():
    try:
        return "valor_real"
    finally:
        return "siempre_esto"    # ← siempre devuelve esto, oculta el valor real

# ✅ finally solo para limpieza, nunca para lógica de negocio
def obtener():
    try:
        resultado = calcular()
        return resultado
    finally:
        limpiar_recursos()    # limpieza, sin return

✅ Resumen y próximos pasos

try/except captura errores sin detener el programa. Captura siempre la excepción más específica. else se ejecuta solo si no hubo error. finally siempre se ejecuta — ideal para liberar recursos. raise lanza excepciones intencionadamente, con tu propio mensaje. Las excepciones personalizadas hacen el código más expresivo. Y with es la forma idiomática de gestionar recursos sin olvidar el cierre.

La siguiente lección: archivos y rutas — leer y escribir ficheros de texto, CSV y JSON con Python.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Manejo de errores en Python: try, except, else, finally y excepciones propias

Las dudas más comunes respondidas de forma clara y directa.

Solo como último recurso, y siempre registrando el error con logging o al menos imprimiéndolo. Capturar Exception a ciegas oculta bugs y hace el código difícil de depurar. La regla: captura la excepción más específica posible. Si necesitas un fallback genérico, ponlo al final después de los except específicos y asegúrate de no silenciar el error sin más.
except sin tipo captura todas las excepciones, incluyendo SystemExit, KeyboardInterrupt y GeneratorExit, que normalmente no deberías interceptar. except Exception captura todas las excepciones derivadas de Exception, que es la clase base de los errores de aplicación. La forma con as e te da acceso al objeto excepción y su mensaje. En la práctica siempre usa al menos except Exception as e para poder inspeccionar el error.
Sí, siempre. El finally se ejecuta antes de que el return salga de la función, antes de que una excepción no capturada se propague hacia arriba, y antes de que un break o continue salga de un bucle. La única excepción es si el intérprete muere abruptamente (os._exit(), corte de luz, señal SIGKILL). Por eso finally es el lugar correcto para liberar recursos: archivos, conexiones, locks.
Cuando quieres distinguir errores de tu dominio de los errores genéricos de Python. Por ejemplo, en una aplicación de pagos puedes tener SaldoInsuficiente, LimiteSuperado o TarjetaExpirada. Esto permite que quien llame tu código capture exactamente el tipo de error que le interesa manejar. La convención es heredar de Exception o de una subclase más específica (ValueError, RuntimeError) según la naturaleza del error.
EAFP significa "Easier to Ask Forgiveness than Permission" (más fácil pedir perdón que permiso). En lugar de comprobar si algo puede hacerse antes de intentarlo (LBYL: Look Before You Leap), Python prefiere intentarlo y capturar el error si ocurre. El código EAFP es más limpio en presencia de concurrencia porque evita race conditions entre la comprobación y la acción. También es más legible cuando el caso de error es infrecuente.
with es azúcar sintáctico para el patrón try/finally de gestión de recursos. Cuando usas with open("archivo") as f: Python garantiza que el archivo se cierra al salir del bloque, tanto si hay error como si no, sin que tengas que escribir el finally explícitamente. Cualquier objeto que implemente __enter__ y __exit__ puede usarse con with. Además de archivos, se usa con conexiones de base de datos, locks de threading, sesiones HTTP y transacciones.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Manejo de errores en Python: try, except, else, finally y excepciones propias? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!