13. Variables temporales innecesarias y estados intermedios confusos

13.1 Objetivo del tema

Las variables temporales pueden mejorar la lectura cuando nombran una idea importante. Pero también pueden agregar ruido, esconder errores o crear estados intermedios difíciles de seguir.

En este tema aprenderemos a distinguir variables útiles de variables innecesarias. También veremos cómo simplificar cálculos, nombrar resultados intermedios con intención y evitar estados que cambian demasiadas veces dentro de una función.

Objetivo práctico: simplificar funciones Python eliminando variables temporales innecesarias y haciendo más claros los estados intermedios importantes.

13.2 Cuándo una variable temporal ayuda

Una variable temporal es útil cuando le da nombre a una idea del dominio o evita repetir una expresión compleja.

subtotal = calcular_subtotal(productos)
descuento = obtener_descuento(cliente)
total_con_descuento = subtotal * (1 - descuento)

En este caso, los nombres explican etapas del cálculo. No son ruido: ayudan a leer el flujo.

13.3 Cuándo una variable temporal estorba

Una variable temporal estorba cuando solo repite una expresión obvia o se usa una sola vez sin mejorar la intención.

def obtener_nombre_completo(nombre, apellido):
    resultado = f"{nombre} {apellido}"
    return resultado

La variable resultado no agrega información. Podemos simplificar:

def obtener_nombre_completo(nombre, apellido):
    return f"{nombre} {apellido}"

13.4 Variables con nombres genéricos

Una variable temporal con nombre genérico suele empeorar la lectura.

def calcular_total(productos):
    x = sum(producto["precio"] * producto["cantidad"] for producto in productos)
    y = x * 1.21
    z = y + 1500
    return z

La función tiene pasos, pero los nombres no dicen nada. Una mejora sería:

def calcular_total(productos):
    subtotal = sum(
        producto["precio"] * producto["cantidad"]
        for producto in productos
    )
    total_con_iva = subtotal * 1.21
    total_con_envio = total_con_iva + 1500
    return total_con_envio

13.5 Estados que cambian demasiadas veces

Cuando una misma variable cambia muchas veces, el lector debe recordar qué representa en cada momento.

def calcular_total(productos, cliente):
    total = 0
    for producto in productos:
        total += producto["precio"] * producto["cantidad"]

    if cliente == "vip":
        total = total * 0.85

    total = total * 1.21
    total = total + 1500
    return round(total, 2)

La variable total primero es subtotal, luego total con descuento, luego total con impuesto y luego total con envío. Conviene nombrar las etapas importantes.

13.6 Nombrar etapas del cálculo

Una versión más clara puede separar estados:

def calcular_total(productos, cliente):
    subtotal = calcular_subtotal(productos)
    total_con_descuento = aplicar_descuento(subtotal, cliente)
    total_con_iva = total_con_descuento * 1.21
    total_con_envio = total_con_iva + 1500
    return round(total_con_envio, 2)

No eliminamos todas las variables. Elegimos nombres que muestran cómo progresa el cálculo.

13.7 Evitar variables bandera innecesarias

Una variable bandera puede indicar una condición, pero a veces complica el flujo.

def tiene_productos_validos(productos):
    encontrado = False
    for producto in productos:
        if producto["cantidad"] > 0:
            encontrado = True
    return encontrado

Podemos retornar apenas encontramos el caso:

def tiene_productos_validos(productos):
    for producto in productos:
        if producto["cantidad"] > 0:
            return True
    return False

O usar any si resulta claro:

def tiene_productos_validos(productos):
    return any(producto["cantidad"] > 0 for producto in productos)

13.8 Usar any y all con criterio

any y all pueden reducir variables temporales y bucles manuales.

def todos_tienen_precio(productos):
    return all(producto["precio"] > 0 for producto in productos)

Pero no conviene forzar expresiones demasiado largas. Si la condición crece, extrae una función con nombre.

def es_producto_valido(producto):
    return producto["precio"] > 0 and producto["cantidad"] > 0


def todos_son_validos(productos):
    return all(es_producto_valido(producto) for producto in productos)

13.9 Variables temporales para depuración

A veces dejamos variables creadas durante la depuración y luego olvidamos eliminarlas.

def calcular_subtotal(productos):
    cantidad_productos = len(productos)
    subtotal = sum(
        producto["precio"] * producto["cantidad"]
        for producto in productos
    )
    return subtotal

Si cantidad_productos no se usa, debe eliminarse. Ruff puede detectar este caso.

python -m ruff check src tests

13.10 Evitar acumuladores cuando hay una expresión clara

Un acumulador explícito puede ser correcto, pero a veces una expresión con sum comunica mejor la intención.

def calcular_subtotal(productos):
    subtotal = 0
    for producto in productos:
        subtotal += producto["precio"] * producto["cantidad"]
    return subtotal

Versión alternativa:

def calcular_subtotal(productos):
    return sum(
        producto["precio"] * producto["cantidad"]
        for producto in productos
    )

Ambas pueden ser válidas. La segunda es más compacta, pero la primera puede ser preferible si luego hay reglas adicionales dentro del bucle.

13.11 Evitar listas temporales innecesarias

A veces creamos una lista intermedia solo para sumarla o contarla.

def calcular_total_productos_activos(productos):
    valores = []
    for producto in productos:
        if producto["activo"]:
            valores.append(producto["precio"] * producto["cantidad"])
    return sum(valores)

Podemos usar una expresión generadora:

def calcular_total_productos_activos(productos):
    return sum(
        producto["precio"] * producto["cantidad"]
        for producto in productos
        if producto["activo"]
    )

13.12 Cuando la variable intermedia mejora la lectura

No hay que eliminar variables por regla automática. Si una expresión es compleja, una variable con buen nombre ayuda.

def puede_recibir_descuento(usuario, compra):
    es_cliente_habilitado = usuario["activo"] and not usuario["bloqueado"]
    supera_monto_minimo = compra["total"] >= 10000
    tiene_email = usuario["email"] != ""

    return es_cliente_habilitado and supera_monto_minimo and tiene_email

Estas variables temporales son útiles porque nombran reglas del dominio.

13.13 Aplicación sobre ventas_demo

En ventas_demo, revisa si calcular_total_venta tiene estados intermedios claros. Una versión razonable podría ser:

def calcular_total_venta(productos, cliente, pais):
    subtotal = calcular_subtotal(productos)
    descuento = obtener_descuento(cliente)
    impuesto = obtener_impuesto(pais)

    total_con_descuento = subtotal * (1 - descuento)
    total_con_impuesto = total_con_descuento * (1 + impuesto)
    total_con_envio = aplicar_envio(total_con_impuesto)

    return round(total_con_envio, 2)

Los nombres muestran la evolución del cálculo. Si todas las etapas se llamaran total, sería más difícil revisar errores.

13.14 Proteger simplificaciones con pruebas

Eliminar variables temporales puede cambiar comportamiento si se altera el orden de operaciones. Ejecuta pruebas antes y después.

python -m pytest

También puedes agregar pruebas específicas para funciones simplificadas:

def test_calcular_subtotal():
    productos = [
        {"precio": 1000, "cantidad": 2},
        {"precio": 500, "cantidad": 3},
    ]

    assert calcular_subtotal(productos) == 3500

13.15 Ejercicio guiado

Simplifica el siguiente código:

def obtener_emails_activos(usuarios):
    resultado = []
    for usuario in usuarios:
        activo = usuario["activo"]
        email = usuario["email"]
        if activo == True:
            if email != "":
                resultado.append(email)
    salida = resultado
    return salida

Una posible mejora es:

def obtener_emails_activos(usuarios):
    emails = []
    for usuario in usuarios:
        if usuario["activo"] and usuario["email"] != "":
            emails.append(usuario["email"])
    return emails

También podríamos usar una comprensión de listas si el equipo la considera clara.

13.16 Versión con comprensión de listas

def obtener_emails_activos(usuarios):
    return [
        usuario["email"]
        for usuario in usuarios
        if usuario["activo"] and usuario["email"] != ""
    ]

Esta versión es compacta y clara para muchos programadores Python. Si la condición fuera más compleja, convendría extraer una función de consulta.

13.17 Ejercicio propuesto

Mejora esta función sin cambiar el comportamiento:

def calcular_importe(items):
    total = 0
    valores = []
    for item in items:
        precio = item["precio"]
        cantidad = item["cantidad"]
        valor = precio * cantidad
        valores.append(valor)
    total = sum(valores)
    resultado = round(total, 2)
    return resultado

Realiza estas tareas:

  • Elimina variables que no agregan intención.
  • Conserva variables que aclaren una regla importante.
  • Evalúa si conviene usar sum con una expresión generadora.
  • Agrega una prueba antes de modificar.
  • Ejecuta Ruff y las pruebas al final.
python -m ruff check src tests
python -m pytest

13.18 Lista de verificación

Antes de continuar, verifica que puedes hacer lo siguiente:

  • Distinguir variables temporales útiles de variables ruidosas.
  • Nombrar estados intermedios importantes.
  • Eliminar variables usadas una sola vez cuando no aportan intención.
  • Reemplazar banderas manuales por retornos tempranos, any o all.
  • Evitar listas intermedias innecesarias.
  • Usar Ruff para detectar variables sin uso.
  • Ejecutar pruebas después de simplificar.

13.19 Conclusión

En este tema vimos que las variables temporales no son buenas o malas por sí mismas. Algunas aclaran la intención; otras agregan ruido o esconden estados confusos.

En el próximo tema estudiaremos el manejo de errores: excepciones silenciadas, genéricas o mal ubicadas.