Skip to content

📙 Clase 12 — Excepciones: try / except

Fase 2 · Funciones y código organizado ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Atrapar errores con try/except para que la app no se caiga.
  • Los tipos de excepción que ya conozco (ValueError, KeyError, IndexError…).
  • else/finally, lanzar errores propios con raise, y validar entradas del usuario en la GUI.

📖 PARTE TEÓRICA

💥 1. ¿Qué es una excepción?

Cuando algo sale mal en tiempo de ejecución, Python lanza una excepción: el programa se detiene y muestra un traceback. Ya conoces varias:

ExcepciónCuándo saltaYa la viste en
ValueErrorvalor con tipo correcto pero contenido inválido: int("abc")tu error real de tkinter
KeyErrorclave inexistente: dic["no_existe"]Clase 04
IndexErroríndice fuera de rango: lista[100]Clase 01
TypeErroroperación con tipo equivocado: (1,2)[0] = 9Clase 05
ZeroDivisionError10 / 0
NameErrorvariable que no existeClase 10

🛡️ 2. try / except: atrapar en vez de morir

python
try:
    edad = int("abc")            # esto EXPLOTA…
except ValueError as e:
    print("Atrapado:", e)        # …pero lo atrapamos y el programa SIGUE
# Atrapado: invalid literal for int() with base 10: 'abc'

El patrón útil de verdad — una función que convierte "con red de seguridad":

python
def convertir(texto):
    try:
        return int(texto)
    except ValueError:
        return None              # señal de "no se pudo"

print(convertir("42"))   # 42
print(convertir("x"))    # None   ← el que llama decide qué hacer

⚠️ Atrapa lo específico. except ValueError: ✅. Un except: pelado (o except Exception: sin motivo) esconde todos los errores, incluidos tus bugs — luego no sabes por qué "no hace nada". Regla: atrapa solo lo que sabes manejar.

🧱 3. La estructura completa: else y finally

python
try:
    n = int("7")
except ValueError:
    print("número inválido")     # SOLO si hubo error
else:
    print("sin error, n =", n)   # SOLO si NO hubo error
finally:
    print("esto corre SIEMPRE")  # haya o no error (limpieza: cerrar archivos/conexiones)
# sin error, n = 7
# esto corre SIEMPRE
try:      lo que puede fallar
except:   ├─ si FALLÓ → manejar
else:     ├─ si NO falló → continuar
finally:  └─ SIEMPRE → limpiar

📣 4. raise: lanzar tus propios errores

Tu propia lógica también puede rechazar datos inválidos lanzando una excepción:

python
def poner_edad(edad):
    if edad < 0:
        raise ValueError("La edad no puede ser negativa")
    return edad

try:
    poner_edad(-5)
except ValueError as e:
    print(e)      # La edad no puede ser negativa

🧪 Tip de entrevista: "¿Diferencia entre error de sintaxis y excepción?" → El de sintaxis impide arrancar el programa (SyntaxError al parsear); la excepción ocurre en ejecución y puede atraparse con try/except.


🖥️ EN TU APP DE ESCRITORIO

En una GUI, una excepción no atrapada dentro de un callback hace que la acción falle en silencio (el traceback se va a la consola y el usuario no ve nada). El patrón profesional: atrapar y avisar amablemente.

python
def calcular():
    try:
        cantidad = int(entry_cantidad.get())     # el usuario pudo escribir "abc"
    except ValueError:
        label_error.configure(text="⚠️ Ingresa un número válido", text_color="red")
        return                                   # early return (Clase 08)

    label_error.configure(text="")               # limpiar el aviso
    label_total.configure(text=f"Total: {cantidad * 10}")

El flujo del especialista:

entrada del usuario ─▶ try convertir/validar ─▶ ¿falló?
                                            ├─ sí → mensaje amable en la GUI + return
                                            └─ no → ejecutar la acción

💡 Conecta con tus errores reales: en vez de que la app muera con un ValueError, ahora la atrapas y muestras un mensaje. Cada error de tu carpeta 06-Errores/ es un candidato a try/except bien puesto.


🗄️ CON BASE DE DATOS (caso de uso)

Las operaciones de BD pueden fallar (archivo bloqueado, tabla inexistente, dato duplicado). sqlite3 tiene su propia familia de excepciones:

python
import sqlite3

try:
    con = sqlite3.connect("app.db")
    con.execute("INSERT INTO contactos (email) VALUES (?)", ("ana@x.com",))
    con.commit()
except sqlite3.IntegrityError:
    print("Ese email ya existe (columna UNIQUE)")   # → mensaje en la GUI
except sqlite3.Error as e:
    print("Error de base de datos:", e)             # cualquier otro problema de BD
finally:
    con.close()                                     # SIEMPRE cerrar la conexión

🗄️ finally: con.close() es el uso clásico de finally. En la Fase 5 verás la forma aún más elegante: el bloque with (context managers).


🏋️ EJERCICIOS CON SOLUCIÓN

Ejercicio 1 — División segura

Escribe dividir(a, b) que devuelva a / b, o None si b es 0 (atrapando la excepción, no con if).

Ver solución
python
def dividir(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None

print(dividir(10, 2))   # 5.0
print(dividir(10, 0))   # None

Ejercicio 2 — Entrada de formulario

Escribe leer_edad(texto) que devuelva (True, edad) si el texto es un entero entre 0 y 120, o (False, mensaje) si no. Usa try/except + validación.

Ver solución
python
def leer_edad(texto):
    try:
        edad = int(texto)
    except ValueError:
        return False, "Debe ser un número"
    if not 0 <= edad <= 120:
        return False, "Edad fuera de rango"
    return True, edad

print(leer_edad("30"))    # (True, 30)
print(leer_edad("abc"))   # (False, 'Debe ser un número')
print(leer_edad("200"))   # (False, 'Edad fuera de rango')

Ejercicio 3 — Rechazar con raise

Escribe retirar(saldo, monto) que lance ValueError("Fondos insuficientes") si monto > saldo, y si no devuelva el nuevo saldo. Atrápalo al llamarla.

Ver solución
python
def retirar(saldo, monto):
    if monto > saldo:
        raise ValueError("Fondos insuficientes")
    return saldo - monto

try:
    retirar(100, 500)
except ValueError as e:
    print(e)              # Fondos insuficientes
print(retirar(100, 30))   # 70

❓ Preguntas y respuestas (autoevaluación)

1. ¿Qué excepción lanza int("hola")?

ValueError (tipo correcto — string —, contenido inválido para convertir).

2. ¿Cuándo corre el bloque finally?

Siempre: haya o no excepción. Es para limpieza (cerrar archivos, conexiones).

3. ¿Por qué es mala idea un except: pelado?

Atrapa todo, incluidos bugs tuyos, y los esconde. Atrapa solo excepciones específicas que sabes manejar.

4. ¿Para qué usas raise en tu propia lógica?

Para rechazar datos/estados inválidos con un error claro que el llamador pueda atrapar.


📎 Apuntes relacionados

➡️ Siguiente

¡Fase 2 lista! 🎉 Sigue la Fase 3 · POO: Clase 13 · Clases y objetos.