Capítulo 10: Control de Daños (Excepciones)

Fig. 10.1: try/except: la trampa que rescata los errores.
Las cosas se rompen. Siempre.
Un cliente pide “cinco” cajas en lugar de 5. Intentas buscar en un casillero que no existe. El archivo de inventario desapareció sin dejar rastro.
Si no tienes un plan, tu programa colapsa (crash). Se detiene por completo y muestra un mensaje rojo horrible (Traceback). Ningún almacén que se respete opera así.
Y a partir de este capítulo, el gafete cambia de pecho. Hasta aquí el Gerente del Almacén era Python: tú entregabas formularios y él los ejecutaba al pie de la letra. Pero llevas siete capítulos operando este almacén, y la gestión de crisis es trabajo de gestión: el puesto ahora es tuyo. Python sigue en el piso, fiel y literal como siempre, convertido en tu maquinaria.
Como Gerente, no puedes paralizar toda la operación porque una sola caja se cayó. Necesitas Protocolos de Seguridad, y en Python eso tiene nombre propio.
El Bloque de Seguridad (try / except)
Python te deja “intentar” una operación riesgosa con un plan B ya listo si algo falla. Esa es toda la idea: preparar la respuesta antes de que ocurra el problema.
Estructura Básica
try:
# 1. Zona de Riesgo: Intenta hacer esto
cantidad = int(input("¿Cuántas cajas quieres? "))
print(f"Procesando {cantidad} cajas...")
except ValueError:
# 2. Plan de Emergencia: Si falla (y SOLO si falla), haz esto
print(
"¡Error! Eso no es un número. Por favor escribe un número (p. ej. 5)."
)
Si el usuario escribe “hola”, Python detecta el problema en la línea int(). En lugar de colapsar, salta de inmediato al bloque except. El resto del almacén sigue funcionando.
Tipos de Accidentes (Excepciones Comunes)
Cada accidente tiene nombre propio. Conocerlos de memoria convierte un traceback en un diagnóstico inmediato.
| El Problema | El Nombre Técnico (Exception) | Ejemplo |
|---|---|---|
| Dato Incorrecto | ValueError | int("cinco") |
| Mezcla Imposible | TypeError | "50" + 5 |
| Llave Perdida | KeyError | dic["no_existe"] |
| Archivo Fantasma | FileNotFoundError | open("no_existe.txt") |
| División Imposible | ZeroDivisionError | 100 / 0 |
Manejando Múltiples Riesgos
Una zona de riesgo puede disparar distintos tipos de accidentes. Para eso existen múltiples bloques except:
try:
cajas_totales = 500
personas = int(input("¿Entre cuántos dividimos? "))
cajas_por_persona = cajas_totales / personas
print(f"Tocan {cajas_por_persona} cajas cada uno.")
except ValueError:
print("¡Debes escribir un número!")
except ZeroDivisionError:
print("¡No puedes dividir entre cero personas!")
Interrogar al Accidente (as e)
Hasta ahora atrapas el error y anuncias tu propio mensaje. A veces quieres el parte oficial: qué dijo Python exactamente. Para eso, dale un nombre al accidente con as:
try:
precio = float(input("Precio: "))
except ValueError as e:
print(f"Dato rechazado. El sistema reportó: {e}")
# El sistema reportó: could not convert string to float: 'caro'
La variable e contiene el objeto del error; al imprimirla obtienes su mensaje. Úsala cuando el detalle ayude a diagnosticar (registros, avisos técnicos). Para el usuario final, tu mensaje en español suele servir más que el parte crudo.
La Jerarquía: Exception es el Padre de Todos
Los accidentes están organizados en familia. ValueError, KeyError, ZeroDivisionError y casi todos los demás descienden de un ancestro común: Exception. Por eso existe este atajo, y por eso hay que usarlo con criterio:
try:
procesar_lote()
except Exception as e:
print(f"Algo falló: {e}")
¡Ojo!:
except Exception:atrapa cualquier accidente ordinario, pero deja pasar las señales de control (como el Ctrl+C con que el usuario corta el programa). Elexcept:a secas que este capítulo condena más abajo atrapa hasta eso. Regla de escalera: primero el error específico; si realmente necesitas red genérica, que seaexcept Exception as ey que al menos registre el mensaje: nunca lo silencies del todo.
El Protocolo de Limpieza (finally)
Hay tareas que no admiten excusas: deben ejecutarse sin importar si hubo error o no. Cerrar la puerta del almacén al salir, por ejemplo. (El código usa open(), que abre un archivo; todavía no la conoces. Por ahora basta con saber que abre un recurso que hay que cerrar.)
archivo = None
try:
archivo = open("inventario.txt", "r")
# ... trabajar con el archivo ...
except FileNotFoundError:
print("No encontré el archivo.")
finally:
# Esto se ejecuta SIEMPRE.
if archivo:
archivo.close()
print("Sistema de archivos cerrado.")
El bloque finally es tu garantía de cierre. No es opcional cuando manejas recursos externos. Y aquí viene un adelanto que te va a gustar: el with open(...) del Capítulo 11 es exactamente este try/finally empaquetado en una línea. Cuando lo veas, no será magia nueva: será este protocolo, automatizado.
(En la edición web ese inventario.txt nunca existe entre una ejecución y otra: la memoria del navegador arranca vacía cada vez, así que aquí siempre caes en el except. En tu terminal, con un archivo real de por medio, depende de si en verdad está ahí.)
Lanzar tus Propias Alertas (raise)
Hasta ahora has atrapado errores que Python lanza. Pero a veces eres tú quien debe declarar el error. Si llega un dato que viola las reglas del almacén (una cantidad negativa, un precio imposible), no esperes a que algo explote más adelante: levanta la alerta en el acto con raise.
def registrar_stock(cantidad: int) -> None:
if cantidad < 0:
raise ValueError("El stock no puede ser negativo")
print(f"Stock registrado: {cantidad}")
raise ValueError(...) detiene la función en seco y dispara el mismo tipo de accidente que ya sabes atrapar. Quien llame a registrar_stock(-5) puede envolverlo en un try / except ValueError y reaccionar. Esta es la base de la validación: rechazar lo inválido en la puerta, con un mensaje claro, en vez de dejar que un dato corrupto se cuele en el almacén.
¿Atrapar o dejar caer?
Ahora que sabes atrapar errores, viene la tentación de atraparlos todos. Resístela. Un try / except no es un paraguas para taparlo todo: es un protocolo para accidentes previsibles que sabes manejar. Atrapar a ciegas no arregla los errores, los esconde, y un error escondido cuesta diez veces más que uno que explota en tu cara.
Hay dos clases de accidentes y se tratan distinto. Cuando el problema viene de afuera y tú no lo controlas (el operario escribió “cinco”, el archivo no está, la red se cayó), atrápalo: es esperable y tienes un plan B. Cuando el problema es un bug tuyo (escribiste mal el nombre de una variable, te equivocaste en la lógica), déjalo caer. Ese traceback rojo es tu mejor pista: te dice exactamente dónde está la falla para que la corrijas. Si lo atrapas y lo silencias, el bug sigue ahí, solo que ahora invisible.
De ahí sale la regla que más se viola al empezar: atrapa solo lo que nombras. Esto es una trampa, no un protocolo:
# MAL: el cazatodo silencioso
try:
procesar_pedido(pedido)
except: # atrapa absolutamente todo, hasta tus propios bugs
pass # ...y lo tira a la basura sin decir nada
¡Ojo!: ese
except:a secas se traga elValueErrorque esperabas, sí, pero también el nombre que escribiste mal hace cinco minutos, y nunca te enteras. Nombra el accidente que sabes manejar y deja que el resto suba a avisarte.
Así se ve en la práctica: el nombre mal escrito no truena, simplemente desaparece.
try:
total = precio_totl # nombre mal escrito: la variable real es "precio"
except:
pass
print("Programa terminado sin problemas")
Corre eso y ves un único mensaje, sin rastro del bug: Programa terminado sin problemas. Quita el except: y el mismo error sí te avisa:
total = precio_totl
print("Programa terminado sin problemas")
Traceback (most recent call last):
File "almacen.py", line 1, in <module>
total = precio_totl
^^^^^^^^^^^
NameError: name 'precio_totl' is not defined
# BIEN: atrapas lo que entiendes, lo demás sube a la superficie
try:
procesar_pedido(pedido)
except ValueError:
print("El pedido traía un dato inválido, lo rechazo.")
La pregunta antes de escribir un except es siempre la misma: “si esto falla, ¿tengo de verdad algo mejor que hacer que mostrar el error?”. Si la respuesta es no, no lo atrapes. Dejar caer también es una decisión de diseño.
Reto de Gerente: El Portero Robusto
Tu misión está en los Ejercicios del capítulo: construir una petición de datos indestructible que sobreviva a cualquier cosa que el operario escriba. Ve a construirla antes de seguir.