SolveConPython

Python Reto #20 — Manejo robusto de errores (try/except bien hecho)

Nivel: Intermedio
Tema: Excepciones, validación, mensajes útiles, fallar rápido vs. tolerancia, tests con pytest
Objetivo: Aprender a manejar errores de forma profesional: capturar lo necesario, no ocultar fallos, y devolver resultados consistentes.

Reto #20 Manejo robusto de errores (try except bien hecho)
Reto #20 Manejo robusto de errores (try except bien hecho)

Enunciado

Crea una función llamada dividir_seguro(a, b) que:

  1. Reciba dos valores: a y b.
  2. Si a o b es None, lance TypeError.
  3. Si a o b no son numéricos (int o float), lance TypeError.
  4. Si b es 0, no debe lanzar ZeroDivisionError. En su lugar debe devolver None.
  5. Si b no es 0, debe devolver el resultado de a / b como float.
  6. No debe usar un except: genérico que oculte errores inesperados.

La clave del reto: usar try/except de forma mínima y específica.

Ejemplos

  • dividir_seguro(10, 2)5.0
  • dividir_seguro(3, 2)1.5
  • dividir_seguro(10, 0)None
  • dividir_seguro(None, 2)TypeError
  • dividir_seguro("10", 2)TypeError

Pistas

  1. Valida tipos antes de dividir.
  2. Puedes evitar el try/except comprobando b == 0 antes.
  3. Si usas try/except, captura únicamente ZeroDivisionError.

Solución explicada (paso a paso)

  1. Rechazamos None con TypeError (entrada inválida).
  2. Validamos que a y b sean numéricos.
  3. Si b es 0, devolvemos None para evitar el error.
  4. Si no, devolvemos float(a / b).

Este enfoque evita try/except innecesarios y hace el comportamiento totalmente predecible.

Python
def dividir_seguro(a, b) -> float | None:
"""
Divide a entre b de forma segura.
Reglas:
- None en a o b -> TypeError
- a y b deben ser int o float -> TypeError
- b == 0 -> None
- caso normal -> float(a / b)
"""
if a is None or b is None:
raise TypeError("Los parámetros 'a' y 'b' no pueden ser None.")
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Los parámetros 'a' y 'b' deben ser numéricos (int o float).")
if b == 0:
return None
return float(a / b)

Python
import pytest
from reto_20_dividir_seguro import dividir_seguro
def test_division_entera():
assert dividir_seguro(10, 2) == 5.0
def test_division_decimal():
assert dividir_seguro(3, 2) == 1.5
def test_division_por_cero_devuelve_none():
assert dividir_seguro(10, 0) is None
def test_none_lanza_typeerror():
with pytest.raises(TypeError):
dividir_seguro(None, 2)
with pytest.raises(TypeError):
dividir_seguro(2, None)
def test_tipo_incorrecto_lanza_typeerror():
with pytest.raises(TypeError):
dividir_seguro("10", 2)
with pytest.raises(TypeError):
dividir_seguro(10, "2")

Ejecuta:

  • pytest -q

Variante adicional (opcional): usando try/except bien hecho

Si quieres practicar try/except de forma explícita (sin ocultar errores):

def dividir_seguro_try(a, b) -> float | None:
if a is None or b is None:
raise TypeError("Los parámetros 'a' y 'b' no pueden ser None.")
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Los parámetros 'a' y 'b' deben ser numéricos (int o float).")

try:
return float(a / b)
except ZeroDivisionError:
return None

Lo que aprendiste

  • Cuándo evitar try/except y validar primero
  • Cómo capturar excepciones específicas sin “tragar” errores
  • Diseñar funciones predecibles (contratos claros)
  • Tests para comportamientos esperados y casos borde

Accede al código completo y a los tests en GitHub para ejecutar y modificar la solución localmente.

A partir de aquí, estás listo para Intermedio+.
El siguiente paso recomendado es Reto #21 — Parsear logs y extraer métricas (archivos + parsing + agregación), un caso muy real de trabajo.