Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Usa la rueda del ratón o gestos táctiles para hacer zoom • Arrastra para mover

Retorno de valores

🧭 Navegación:

¡Bienvenido al departamento de productos terminados de nuestro almacén! Has aprendido a crear máquinas (funciones) y configurarlas (parámetros), pero ahora necesitas que te entreguen exactamente lo que necesitas. El return es como el área de entrega de tus máquinas, donde recogen el producto final de su trabajo.

📦 ¿Qué es el valor de retorno?

Imagina que tienes una máquina empaquetadora en tu almacén. Puedes darle productos para empaquetar, pero lo que realmente te interesa es recibir de vuelta el paquete terminado para poder usarlo. El return es exactamente eso: la forma en que tu función te entrega el resultado de su trabajo.

# Máquina SIN retorno (solo hace ruido, no entrega nada útil)
def procesar_venta_inutil(producto, precio):
    """Esta máquina solo hace ruido pero no entrega nada"""
    total = precio * 1.16  # Calcula con impuesto
    print(f"Procesando {producto}: ${total:.2f}")
    # ¡No hay return! El cálculo se pierde

# Máquina CON retorno (entrega el producto terminado)
def procesar_venta_util(producto, precio):
    """Esta máquina calcula Y entrega el resultado"""
    total = precio * 1.16  # Calcula con impuesto
    return total  # ¡ENTREGA el resultado!

# Comparación de uso
procesar_venta_inutil("Laptop", 1000)     # Solo imprime, no puedo usar el resultado
total_venta = procesar_venta_util("Laptop", 1000)  # ¡Ahora puedo usar el resultado!

print(f"Puedo usar este total para más cálculos: ${total_venta:.2f}")

🔄 Tipos de retorno

1. Funciones sin retorno explícito (None)

Son como máquinas de servicio que realizan una acción pero no entregan un producto físico:

def mostrar_inventario(productos):
    """Máquina que muestra información pero no retorna nada útil"""
    print("=== INVENTARIO ACTUAL ===")
    for producto in productos:
        print(f"- {producto['nombre']}: {producto['stock']} unidades")
    print("=== FIN DEL INVENTARIO ===")
    # No hay return explícito, por lo que retorna None

def registrar_venta(producto, cantidad):
    """Máquina que registra pero no entrega confirmación"""
    timestamp = datetime.now()
    print(f"[{timestamp}] Venta registrada: {cantidad} x {producto}")
    # Tampoco hay return, retorna None implícitamente

# Estas funciones retornan None
resultado1 = mostrar_inventario([{"nombre": "Laptop", "stock": 5}])
resultado2 = registrar_venta("Mouse", 3)

print(f"resultado1: {resultado1}")  # None
print(f"resultado2: {resultado2}")  # None

2. Funciones con retorno de un solo valor

Son como máquinas especializadas que entregan exactamente un producto:

def calcular_descuento(precio, porcentaje):
    """Máquina calculadora que entrega UN resultado"""
    descuento = precio * (porcentaje / 100)
    return descuento

def obtener_precio_final(precio_base, descuento, impuesto=0.16):
    """Máquina que entrega el precio procesado"""
    precio_con_descuento = precio_base - descuento
    precio_final = precio_con_descuento * (1 + impuesto)
    return precio_final

def validar_codigo_producto(codigo):
    """Máquina validadora que entrega True o False"""
    es_valido = len(codigo) == 6 and codigo[:3].isupper() and codigo[3:].isdigit()
    return es_valido

# Usando las máquinas
descuento_calculado = calcular_descuento(1000, 15)  # Retorna 150.0
precio_final = obtener_precio_final(1000, descuento_calculado)  # Retorna 986.0
es_codigo_valido = validar_codigo_producto("LAP001")  # Retorna True

print(f"Descuento: ${descuento_calculado}")
print(f"Precio final: ${precio_final:.2f}")
print(f"Código válido: {es_codigo_valido}")

3. Funciones con múltiples valores de retorno

Son como máquinas de línea de producción que entregan varios productos a la vez:

def analizar_venta(precio, cantidad):
    """Máquina que entrega múltiples métricas de una vez"""
    subtotal = precio * cantidad
    impuesto = subtotal * 0.16
    total = subtotal + impuesto
    
    # Retornamos múltiples valores como una tupla
    return subtotal, impuesto, total

def obtener_estadisticas_producto(ventas_producto):
    """Máquina que entrega estadísticas completas"""
    total_vendido = sum(ventas_producto)
    promedio = total_vendido / len(ventas_producto) if ventas_producto else 0
    maximo = max(ventas_producto) if ventas_producto else 0
    minimo = min(ventas_producto) if ventas_producto else 0
    
    return total_vendido, promedio, maximo, minimo

# Recibiendo múltiples valores
subtotal, impuesto, total = analizar_venta(100, 5)
print(f"Subtotal: ${subtotal}, Impuesto: ${impuesto:.2f}, Total: ${total:.2f}")

# También puedes recibirlos como una sola tupla
resultado_analisis = analizar_venta(200, 3)
print(f"Resultado completo: {resultado_analisis}")

# Desempaquetar estadísticas
ventas = [120, 85, 200, 150, 95, 180]
total, promedio, max_venta, min_venta = obtener_estadisticas_producto(ventas)
print(f"Total: {total}, Promedio: {promedio:.1f}, Max: {max_venta}, Min: {min_venta}")

4. Funciones que retornan estructuras de datos complejas

Son como máquinas ensambladoras que entregan productos complejos y organizados:

def crear_reporte_producto(codigo, nombre, ventas_mes):
    """Máquina que ensambla un reporte completo"""
    reporte = {
        "codigo": codigo,
        "nombre": nombre,
        "ventas": {
            "total_unidades": sum(ventas_mes),
            "promedio_diario": sum(ventas_mes) / len(ventas_mes),
            "mejor_dia": max(ventas_mes),
            "peor_dia": min(ventas_mes)
        },
        "estado": "ACTIVO" if sum(ventas_mes) > 0 else "INACTIVO",
        "recomendacion": "Reabastecer" if sum(ventas_mes) > 100 else "Mantener stock"
    }
    return reporte

def procesar_pedido_completo(productos_pedido):
    """Máquina que entrega un pedido completamente procesado"""
    productos_procesados = []
    total_pedido = 0
    
    for producto in productos_pedido:
        producto_procesado = {
            "codigo": producto["codigo"],
            "nombre": producto["nombre"],
            "cantidad": producto["cantidad"],
            "precio_unitario": producto["precio"],
            "subtotal": producto["precio"] * producto["cantidad"]
        }
        productos_procesados.append(producto_procesado)
        total_pedido += producto_procesado["subtotal"]
    
    pedido_final = {
        "productos": productos_procesados,
        "resumen": {
            "total_productos": len(productos_procesados),
            "total_unidades": sum(p["cantidad"] for p in productos_procesados),
            "subtotal": total_pedido,
            "impuesto": total_pedido * 0.16,
            "total_final": total_pedido * 1.16
        },
        "metadata": {
            "fecha_procesamiento": datetime.now().isoformat(),
            "status": "PROCESADO"
        }
    }
    
    return pedido_final

# Usando máquinas complejas
ventas_laptop = [12, 8, 15, 10, 20, 18, 14]
reporte = crear_reporte_producto("LAP001", "Laptop Gaming", ventas_laptop)

print("📊 REPORTE DE PRODUCTO:")
print(f"Producto: {reporte['nombre']} ({reporte['codigo']})")
print(f"Total vendido: {reporte['ventas']['total_unidades']} unidades")
print(f"Promedio diario: {reporte['ventas']['promedio_diario']:.1f}")
print(f"Estado: {reporte['estado']}")
print(f"Recomendación: {reporte['recomendacion']}")

# Procesando un pedido complejo
productos_para_pedido = [
    {"codigo": "LAP001", "nombre": "Laptop", "cantidad": 2, "precio": 1500},
    {"codigo": "MOU001", "nombre": "Mouse", "cantidad": 2, "precio": 35},
    {"codigo": "TEC001", "nombre": "Teclado", "cantidad": 1, "precio": 120}
]

pedido_procesado = procesar_pedido_completo(productos_para_pedido)
print(f"\n💰 TOTAL DEL PEDIDO: ${pedido_procesado['resumen']['total_final']:.2f}")

🎯 Patrones de retorno avanzados

1. Retorno condicional

Las máquinas pueden entregar diferentes tipos de productos según las condiciones:

def buscar_producto(codigo, inventario):
    """
    Máquina buscadora que retorna diferentes cosas según lo que encuentre
    """
    for producto in inventario:
        if producto["codigo"] == codigo:
            # Si lo encuentra, retorna el producto completo
            return producto
    
    # Si no lo encuentra, retorna None
    return None

def procesar_compra(total, dinero_recibido):
    """Máquina que retorna diferentes resultados según el escenario"""
    if dinero_recibido < total:
        # Retorna diccionario con error
        return {
            "exitoso": False,
            "error": "Dinero insuficiente",
            "faltante": total - dinero_recibido
        }
    elif dinero_recibido == total:
        # Retorna confirmación sin cambio
        return {
            "exitoso": True,
            "cambio": 0,
            "mensaje": "Pago exacto recibido"
        }
    else:
        # Retorna confirmación con cambio
        return {
            "exitoso": True,
            "cambio": dinero_recibido - total,
            "mensaje": "Compra procesada exitosamente"
        }

# Ejemplos de uso
inventario = [
    {"codigo": "LAP001", "nombre": "Laptop", "precio": 1500},
    {"codigo": "MOU001", "nombre": "Mouse", "precio": 35}
]

# Búsqueda exitosa
producto_encontrado = buscar_producto("LAP001", inventario)
if producto_encontrado:
    print(f"Producto encontrado: {producto_encontrado['nombre']}")
else:
    print("Producto no encontrado")

# Búsqueda fallida
producto_no_existe = buscar_producto("XYZ999", inventario)
if producto_no_existe is None:
    print("Este producto no existe en el inventario")

# Diferentes escenarios de compra
resultado1 = procesar_compra(100, 80)    # Dinero insuficiente
resultado2 = procesar_compra(100, 100)   # Pago exacto
resultado3 = procesar_compra(100, 120)   # Con cambio

for resultado in [resultado1, resultado2, resultado3]:
    print(f"Compra exitosa: {resultado['exitoso']}")
    if not resultado['exitoso']:
        print(f"Error: {resultado['error']}")
    else:
        print(f"Cambio: ${resultado['cambio']}")

2. Retorno anticipado (Early Return)

Como máquinas con controles de calidad que se detienen temprano si detectan problemas:

def validar_y_procesar_pedido(pedido):
    """
    Máquina con múltiples puntos de salida para diferentes validaciones
    """
    # Validación 1: ¿Existe el pedido?
    if not pedido:
        return {"error": "Pedido vacío", "codigo": "E001"}
    
    # Validación 2: ¿Tiene productos?
    if not pedido.get("productos"):
        return {"error": "Pedido sin productos", "codigo": "E002"}
    
    # Validación 3: ¿Todos los productos tienen precio?
    for producto in pedido["productos"]:
        if "precio" not in producto or producto["precio"] <= 0:
            return {"error": f"Precio inválido para {producto.get('nombre', 'producto desconocido')}", "codigo": "E003"}
    
    # Validación 4: ¿El cliente existe?
    if not pedido.get("cliente"):
        return {"error": "Cliente no especificado", "codigo": "E004"}
    
    # Si pasó todas las validaciones, procesar normalmente
    total = sum(p["precio"] * p["cantidad"] for p in pedido["productos"])
    
    return {
        "exitoso": True,
        "total": total,
        "productos_procesados": len(pedido["productos"]),
        "cliente": pedido["cliente"]
    }

# Pruebas con diferentes pedidos
pedidos_prueba = [
    None,  # Pedido vacío
    {"cliente": "Ana"},  # Sin productos
    {"productos": [{"nombre": "Laptop", "precio": 0}], "cliente": "Juan"},  # Precio inválido
    {"productos": [{"nombre": "Mouse", "precio": 35, "cantidad": 2}]},  # Sin cliente
    {"productos": [{"nombre": "Laptop", "precio": 1500, "cantidad": 1}], "cliente": "María"}  # Válido
]

for i, pedido in enumerate(pedidos_prueba, 1):
    resultado = validar_y_procesar_pedido(pedido)
    print(f"\nPedido {i}:")
    if resultado.get("exitoso"):
        print(f"✅ Procesado exitosamente - Total: ${resultado['total']}")
    else:
        print(f"❌ Error: {resultado['error']} (Código: {resultado['codigo']})")

3. Funciones generadoras (yield)

Como máquinas de producción continua que van entregando productos uno por uno:

def generar_reportes_mensuales(ventas_anuales):
    """
    Máquina generadora que produce reportes mes por mes
    """
    meses = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
             "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]
    
    for i, ventas_mes in enumerate(ventas_anuales):
        reporte = {
            "mes": meses[i],
            "numero_mes": i + 1,
            "ventas": ventas_mes,
            "promedio_diario": ventas_mes / 30,
            "estado": "Excelente" if ventas_mes > 10000 else "Bueno" if ventas_mes > 5000 else "Bajo"
        }
        yield reporte  # Entrega un reporte y pausa hasta la siguiente solicitud

def procesar_inventario_grande(productos):
    """
    Máquina que procesa inventarios grandes de forma eficiente
    """
    for producto in productos:
        # Procesamiento complejo del producto
        producto_procesado = {
            "codigo": producto["codigo"],
            "nombre": producto["nombre"],
            "valor_total": producto["precio"] * producto["stock"],
            "categoria_valor": "Alto" if producto["precio"] > 500 else "Medio" if producto["precio"] > 100 else "Bajo",
            "necesita_reposicion": producto["stock"] < 10
        }
        
        yield producto_procesado  # Entrega procesado de a uno

# Usando generadores
ventas_por_mes = [8500, 9200, 12000, 11500, 13000, 14500, 
                  15000, 13500, 12800, 11200, 10500, 16000]

print("📅 REPORTES MENSUALES:")
for reporte in generar_reportes_mensuales(ventas_por_mes):
    print(f"{reporte['mes']}: ${reporte['ventas']:,} ({reporte['estado']})")
    if reporte['numero_mes'] == 6:  # Solo mostrar primeros 6 meses
        break

# Procesando inventario grande
inventario_grande = [
    {"codigo": "LAP001", "nombre": "Laptop Gaming", "precio": 1500, "stock": 8},
    {"codigo": "LAP002", "nombre": "Laptop Office", "precio": 800, "stock": 5},
    {"codigo": "MOU001", "nombre": "Mouse", "precio": 35, "stock": 25},
    {"codigo": "TEC001", "nombre": "Teclado", "precio": 120, "stock": 12}
]

print("\n📦 INVENTARIO PROCESADO:")
for producto_procesado in procesar_inventario_grande(inventario_grande):
    print(f"{producto_procesado['nombre']}: ${producto_procesado['valor_total']} (Categoría: {producto_procesado['categoria_valor']})")
    if producto_procesado['necesita_reposicion']:
        print(f"   ⚠️ Requiere reposición")

🛠️ Mejores prácticas para retornos

1. Consistencia en el tipo de retorno

# ❌ Mal: Retorna diferentes tipos según el caso
def buscar_precio_mal(codigo):
    if codigo == "LAP001":
        return 1500.00  # Retorna float
    elif codigo == "MOU001":
        return "35 pesos"  # Retorna string
    else:
        return None  # Retorna None

# ✅ Bien: Siempre retorna el mismo tipo o estructura
def buscar_precio_bien(codigo):
    precios = {"LAP001": 1500.00, "MOU001": 35.00}
    
    if codigo in precios:
        return {"encontrado": True, "precio": precios[codigo]}
    else:
        return {"encontrado": False, "precio": None}

2. Documentación clara del retorno

def calcular_metricas_venta(ventas_diarias):
    """
    Calcula métricas completas de ventas.
    
    Args:
        ventas_diarias (list): Lista de ventas por día
    
    Returns:
        dict: Diccionario con las siguientes claves:
            - total (float): Suma total de ventas
            - promedio (float): Promedio diario de ventas
            - maximo (float): Día de mayor venta
            - minimo (float): Día de menor venta
            - dias_objetivo (int): Días que superaron objetivo de $1000
            - tendencia (str): "CRECIENTE", "DECRECIENTE", o "ESTABLE"
    
    Raises:
        ValueError: Si la lista de ventas está vacía
    """
    if not ventas_diarias:
        raise ValueError("La lista de ventas no puede estar vacía")
    
    total = sum(ventas_diarias)
    promedio = total / len(ventas_diarias)
    maximo = max(ventas_diarias)
    minimo = min(ventas_diarias)
    dias_objetivo = sum(1 for venta in ventas_diarias if venta > 1000)
    
    # Calcular tendencia comparando primera y segunda mitad
    mitad = len(ventas_diarias) // 2
    primera_mitad = sum(ventas_diarias[:mitad]) / mitad
    segunda_mitad = sum(ventas_diarias[mitad:]) / (len(ventas_diarias) - mitad)
    
    if segunda_mitad > primera_mitad * 1.1:
        tendencia = "CRECIENTE"
    elif segunda_mitad < primera_mitad * 0.9:
        tendencia = "DECRECIENTE"
    else:
        tendencia = "ESTABLE"
    
    return {
        "total": total,
        "promedio": promedio,
        "maximo": maximo,
        "minimo": minimo,
        "dias_objetivo": dias_objetivo,
        "tendencia": tendencia
    }

3. Manejo de casos especiales

def dividir_seguro(dividendo, divisor):
    """
    División segura que maneja casos especiales.
    
    Returns:
        tuple: (exito: bool, resultado: float o str)
    """
    if divisor == 0:
        return False, "Error: División por cero"
    
    if not isinstance(dividendo, (int, float)) or not isinstance(divisor, (int, float)):
        return False, "Error: Los argumentos deben ser números"
    
    try:
        resultado = dividendo / divisor
        return True, resultado
    except Exception as e:
        return False, f"Error inesperado: {str(e)}"

# Uso seguro
exito, resultado = dividir_seguro(10, 3)
if exito:
    print(f"Resultado: {resultado:.2f}")
else:
    print(f"Error en la operación: {resultado}")

🎯 Comprueba tu comprensión

Ejercicio 1: Analizador de ventas

Crea una función que analice las ventas de un producto y retorne información completa:

def analizar_producto_ventas(nombre_producto, ventas_diarias, precio_unitario):
    """
    Analiza las ventas de un producto y retorna métricas completas.
    
    Args:
        nombre_producto: Nombre del producto
        ventas_diarias: Lista con cantidad vendida cada día
        precio_unitario: Precio por unidad
    
    Returns:
        dict: Información completa del análisis
    """
    # Tu código aquí
    pass

# Prueba
ventas = [12, 8, 15, 10, 20, 5, 18]
resultado = analizar_producto_ventas("Laptop Gaming", ventas, 1500)
print(resultado)

Ejercicio 2: Validador con múltiples retornos

Crea una función que valide un pedido y retorne diferentes resultados:

def validar_pedido_completo(pedido_data):
    """
    Valida un pedido completo y retorna el resultado de la validación.
    
    Debe validar:
    - Que existan productos
    - Que todos tengan precio válido
    - Que las cantidades sean positivas
    - Que el total no exceda $10,000
    
    Returns:
        dict: Resultado de validación con estado y detalles
    """
    # Tu código aquí
    pass

# Pruebas
pedido1 = {
    "productos": [
        {"nombre": "Laptop", "precio": 1500, "cantidad": 2},
        {"nombre": "Mouse", "precio": 35, "cantidad": 1}
    ]
}

resultado = validar_pedido_completo(pedido1)
print(resultado)

Ejercicio 3: Generador de reportes

Crea una función generadora que produzca reportes de inventario:

def generar_reportes_inventario(productos):
    """
    Generador que produce reportes de productos uno por uno.
    
    Para cada producto debe calcular:
    - Valor total en inventario
    - Estado del stock (Crítico/Bajo/Normal/Alto)
    - Recomendación de acción
    
    Yields:
        dict: Reporte individual de cada producto
    """
    # Tu código aquí
    pass

# Prueba
inventario = [
    {"codigo": "LAP001", "nombre": "Laptop", "precio": 1500, "stock": 3},
    {"codigo": "MOU001", "nombre": "Mouse", "precio": 35, "stock": 25},
    {"codigo": "TEC001", "nombre": "Teclado", "precio": 120, "stock": 8}
]

for reporte in generar_reportes_inventario(inventario):
    print(reporte)

💡 Consejos para el éxito

  1. Siempre documenta qué retorna tu función: Especifica tipo y estructura en el docstring
  2. Sé consistente: Si una función puede retornar múltiples tipos, usa una estructura consistente
  3. Maneja casos especiales: Considera qué pasa con entradas vacías, nulas o inválidas
  4. Usa nombres descriptivos: El retorno debe ser claro por el nombre de la función
  5. Retorna temprano: Usa early returns para validaciones y casos especiales
  6. Prefire retornar estructuras: Los diccionarios y tuplas nombradas son más claros que múltiples valores sueltos

🎉 ¡Felicitaciones!

Has dominado el área de entrega de tus máquinas digitales. Ahora puedes:

  • ✅ Diseñar funciones que entreguen exactamente lo que necesitas
  • ✅ Manejar múltiples valores de retorno eficientemente
  • ✅ Crear funciones que se adapten a diferentes escenarios
  • ✅ Implementar validaciones con retornos informativos
  • ✅ Usar generadores para procesar grandes cantidades de datos

En la siguiente sección aprenderemos sobre módulos estándar: cómo usar las herramientas prefabricadas que Python te ofrece para hacer tu trabajo más eficiente.


🧭 Navegación: