La salida de un programa también forma parte de su calidad. Un mensaje confuso, un print olvidado o un log con datos incompletos puede hacer difícil entender qué ocurrió cuando aparece un problema.
En este tema veremos cuándo usar print, cuándo usar logging, cómo escribir mensajes de error útiles y cómo separar salida de usuario, diagnóstico técnico y reglas de negocio.
print es útil para scripts simples, ejemplos didácticos o salida final para el usuario. El problema aparece cuando se usa como mecanismo de diagnóstico permanente dentro de funciones de negocio.
def calcular_total(productos):
total = sum(producto.precio * producto.cantidad for producto in productos)
print("total calculado", total)
return total
Ese print puede ensuciar pruebas, consola y procesos automáticos. Si necesitamos diagnóstico, conviene usar logging.
Una función de cálculo debería devolver datos. Otra función puede encargarse de mostrar.
def calcular_total(productos):
return sum(producto.precio * producto.cantidad for producto in productos)
def mostrar_total(total):
print(f"Total: {total:.2f}")
Esto facilita probar el cálculo sin capturar salida por consola.
El módulo logging permite registrar eventos con niveles y configuración. A diferencia de print, puede activarse, desactivarse, redirigirse a archivos o mostrar más contexto.
import logging
logger = logging.getLogger(__name__)
def calcular_total(productos):
total = sum(producto.precio * producto.cantidad for producto in productos)
logger.info("Total calculado: %.2f", total)
return total
En un script o punto de entrada, puedes configurar logging así:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s:%(name)s:%(message)s",
)
Normalmente la configuración se hace una sola vez en el punto de entrada, no dentro de cada módulo.
Los niveles más usados son:
DEBUG: información detallada para diagnóstico.INFO: eventos normales importantes.WARNING: situación extraña, pero recuperable.ERROR: error que impide completar una operación.CRITICAL: error grave que compromete el sistema.Con logging conviene usar parámetros en lugar de construir strings siempre.
logger.info("Venta procesada: cliente=%s total=%.2f", cliente, total)
Evita esto si el mensaje solo sirve para logging:
logger.info(f"Venta procesada: cliente={cliente} total={total}")
La versión con parámetros deja que logging maneje el formateo cuando corresponde.
Dentro de un bloque except, logger.exception registra el traceback automáticamente.
def cargar_productos(ruta):
try:
with open(ruta, encoding="utf-8") as archivo:
return archivo.readlines()
except OSError:
logger.exception("No se pudo leer el archivo de productos: %s", ruta)
raise
Registramos el error y volvemos a lanzarlo. No lo ocultamos.
Un mensaje de error útil debe explicar qué falló y, si corresponde, qué dato provocó el problema.
raise ValueError("La cantidad debe ser positiva")
Más contextual:
raise ValueError(f"La cantidad debe ser positiva: {cantidad}")
Evita mensajes como "error", "falló" o "datos incorrectos" si puedes ser más preciso.
El mensaje técnico para logs no siempre debe ser el mismo que ve el usuario. Un traceback completo puede ser útil para el programador, pero confuso para una persona que usa el sistema.
try:
total = calcular_total(productos)
except ValueError as error:
logger.warning("Datos inválidos al calcular total: %s", error)
print("No se pudo calcular el total. Revisa los productos ingresados.")
El log conserva detalle técnico; la consola muestra una acción comprensible.
Registrar todo puede ser tan malo como no registrar nada. Los logs excesivos dificultan encontrar lo importante.
logger.debug("Entrando a calcular_total")
logger.debug("Producto 1")
logger.debug("Producto 2")
logger.debug("Saliendo de calcular_total")
Prefiere mensajes que aporten contexto real ante problemas o eventos importantes.
No registres contraseñas, tokens, datos personales sensibles o información que no debería quedar almacenada.
# Evitar:
logger.info("Login usuario=%s password=%s", email, password)
Mejor:
logger.info("Intento de login para usuario=%s", email)
En ventas_demo, la función de cálculo no debería imprimir. Si queremos registrar eventos, podemos usar logging:
import logging
logger = logging.getLogger(__name__)
def calcular_total_venta(venta):
subtotal = calcular_subtotal(venta.productos)
logger.debug("Subtotal calculado: %.2f", subtotal)
descuento = obtener_descuento(venta.cliente.tipo)
impuesto = obtener_impuesto(venta.pais)
total = subtotal * (1 - descuento) * (1 + impuesto)
return round(aplicar_envio(total), 2)
El log está en nivel DEBUG porque es información de diagnóstico, no salida normal para el usuario.
Un script principal puede configurar logging y mostrar resultados.
import logging
from ventas import calcular_total_venta
def main():
logging.basicConfig(level=logging.INFO)
total = calcular_total_venta(crear_venta_demo())
print(f"Total final: {total:.2f}")
if __name__ == "__main__":
main()
La configuración y la salida de usuario quedan en el punto de entrada, no dentro del módulo de reglas.
Si una función tiene como responsabilidad mostrar salida, puede probarse con capsys en pytest.
def mostrar_total(total):
print(f"Total final: {total:.2f}")
def test_mostrar_total(capsys):
mostrar_total(1500)
salida = capsys.readouterr()
assert salida.out == "Total final: 1500.00\n"
Pero evita capturar salida para probar funciones que deberían ser puro cálculo.
Mejora este código:
def procesar_venta(productos):
print("procesando")
total = 0
for producto in productos:
if producto["cantidad"] <= 0:
print("error")
return 0
total += producto["precio"] * producto["cantidad"]
print("listo")
return total
Una mejora posible:
import logging
logger = logging.getLogger(__name__)
def procesar_venta(productos):
total = 0
for producto in productos:
if producto["cantidad"] <= 0:
raise ValueError("La cantidad debe ser positiva")
total += producto["precio"] * producto["cantidad"]
logger.info("Venta procesada correctamente")
return total
En ventas_demo, realiza estas tareas:
print dentro de funciones de cálculo o reglas.python -m ruff check src tests
python -m black src tests
python -m pytest
Antes de continuar, verifica que puedes hacer lo siguiente:
print dentro de reglas de negocio.logging.getLogger(__name__).logger.exception dentro de except.En este tema vimos que la salida de un programa debe diseñarse con criterio. print sirve para salida simple, pero el diagnóstico técnico debería usar logging, niveles y mensajes útiles.
En el próximo tema trabajaremos con calidad en scripts Python: entrada, validación, funciones principales y main.