Módulo AVANZADOLección 01 de 06

Programación funcional

map/filter, functools, itertools y funciones puras.

01

Teoría y Fundamentos

Lee y asimila los conceptos clave antes de evaluar

La programación funcional trata el cómputo como la composición de funciones: entradas → salidas, sin estados mutables escondidos. Python no es un lenguaje funcional puro, pero le prestó sus mejores ideas.

Funciones puras: el principio rector

Una función es pura cuando:

  1. Para las mismas entradas devuelve siempre la misma salida
  2. No produce efectos secundarios (no muta nada externo, no imprime, no escribe archivos)
# PURA: determinista, no toca nada externo
def area(base, altura):
    return base * altura

# IMPURA: depende del mundo exterior y lo modifica
total = 0
def acumular(valor):
    global total        # muta estado externo
    total += valor
    return total

Las funciones puras se testean trivialmente, se razonan aisladamente y se paralelizan sin miedo. Aspira a que la mayor parte de tu código sea pura.

map, filter y sorted: El trío de orden superior

Las funciones de orden superior son aquellas que reciben otra función como argumento o devuelven una función. En combinación con lambda, constituyen la columna vertebral del procesamiento de datos en Python.

1. map: Transformación elemento a elemento

map(func, *iterables) aplica la función a cada elemento y devuelve un generador/iterador perezoso que consume memoria O(1):

temperaturas_c = [0, 15, 22, 30, 40]

# Convertir Celsius a Fahrenheit: (C * 9/5) + 32
fahrenheit = list(map(lambda c: (c * 9/5) + 32, temperaturas_c))
print(fahrenheit) # [32.0, 59.0, 71.6, 86.0, 104.0]

# Mapeo con múltiples fuentes en paralelo (se detiene al agotar la más corta)
nombres = ["Ana", "Carlos", "Beatriz"]
apellidos = ["Ruiz", "Gómez", "Torres"]
completos = list(map(lambda n, a: f"{n} {a}", nombres, apellidos))
print(completos) # ['Ana Ruiz', 'Carlos Gómez', 'Beatriz Torres']

2. filter: Selección por predicado booleano

filter(predicado, iterable) evalúa una función que retorna un booleano (True o False) y conserva únicamente aquellos elementos evaluados como verdaderos:

transacciones = [
    {"id": "tx1", "monto": 150000, "aprobada": True},
    {"id": "tx2", "monto": 25000, "aprobada": False},
    {"id": "tx3", "monto": 800000, "aprobada": True},
    {"id": "tx4", "monto": 12000, "aprobada": True},
]

# Filtrar transacciones aprobadas con montos mayores a $100,000
altas_aprobadas = list(filter(lambda tx: tx["aprobada"] and tx["monto"] > 100000, transacciones))
print([tx["id"] for tx in altas_aprobadas]) # ['tx1', 'tx3']

# Tip profesional: filter(None, iterable) elimina todos los elementos falsy (None, 0, "", [], etc.)
datos_sucios = ["python", "", None, "código", False, 42, 0]
limpios = list(filter(None, datos_sucios))
print(limpios) # ['python', 'código', 42]

3. sorted: Ordenamiento determinista con clave de extracción

A diferencia de list.sort() que muta la lista original y solo existe en listas, la función incorporada sorted(iterable, key=..., reverse=...) funciona sobre cualquier iterable (listas, tuplas, diccionarios, sets, generadores) y devuelve siempre una lista nueva:

inventario = [
    {"producto": "Teclado", "stock": 5, "precio": 45.0},
    {"producto": "Mouse", "stock": 25, "precio": 18.5},
    {"producto": "Monitor", "stock": 8, "precio": 220.0},
    {"producto": "Pad", "stock": 25, "precio": 12.0},
]

# Ordenar por precio ascendente
por_precio = sorted(inventario, key=lambda x: x["precio"])

# Ordenar por tupla multicriterio: primero por stock descendente (-x["stock"]), luego por precio ascendente
por_stock_y_precio = sorted(inventario, key=lambda x: (-x["stock"], x["precio"]))
for item in por_stock_y_precio:
    print(f"{item['producto']}: {item['stock']} unid. a ${item['precio']}")
# Mouse: 25 unid. a $18.5
# Pad: 25 unid. a $12.0
# Monitor: 8 unid. a $220.0
# Teclado: 5 unid. a $45.0

¿map/filter vs List Comprehensions?

En la práctica profesional en Python:

  • Las list comprehensions son preferidas por la comunidad para transformaciones y filtros simples ([x * 2 for x in lista if x > 0]) debido a su legibilidad y optimización en CPython.
  • Sin embargo, map y filter son indispensables al trabajar con iteradores infinitos, cuando la función transformadora ya existe como función nombrada (map(int, cadenas)), o cuando se construyen pipelines funcionales puros.
  • sorted(..., key=lambda ...) es la herramienta indiscutible para ordenamiento estructurado.

reduce: plegar a un solo valor

from functools import reduce

numeros = [2, 3, 4, 5]

producto = reduce(lambda acc, n: acc * n, numeros)
print(producto)    # 120 → (((2*3)*4)*5)

reduce acumula: toma dos elementos, los combina en uno, y repite hasta que queda uno solo. Para sumas usa sum() (más claro); para todo lo demás, piensa si un bucle simple no sería más legible.

functools: utilería seria

from functools import lru_cache, partial

@lru_cache(maxsize=None)          # cachea resultados: memoización
def fibonacci(n):
    return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))   # instantáneo con caché; siglos sin ella

int2 = partial(int, base=2)       # pre-fijar argumentos
print(int2("1010"))    # 10
  • @lru_cache: convierte funciones recursivas lentas en rápidas con una línea
  • partial: crea versiones especializadas de funciones

itertools: el álgebra de iteradores

from itertools import chain, pairwise, groupby, count

print(list(chain([1, 2], [3, 4])))     # [1, 2, 3, 4] → concatena
print(list(pairwise([1, 2, 3, 4])))    # [(1, 2), (2, 3), (3, 4)]

# agrupar elementos consecutivos por clave
datos = [("a", 1), ("a", 2), ("b", 3)]
for clave, grupo in groupby(datos, key=lambda x: x[0]):
    print(clave, [x[1] for x in grupo])   # a [1, 2] / b [3]

itertools construye pipelines de iteradores con memoria mínima. Combínalo con generadores y procesas gigas de datos sin sudar.

Inmutabilidad donde puedas

# En vez de mutar, construye nuevos valores
def con_descuento(precios, pct):
    return {nombre: precio * (1 - pct) for nombre, precio in precios.items()}

Resumen

  • Función pura: misma entrada → misma salida, sin efectos secundarios
  • map/filter transforman y filtran; en Python la comprehension suele ganar en legibilidad
  • reduce pliega a un valor; sum/max/min son los casos comunes ya resueltos
  • @lru_cache para memoización; partial para especializar funciones
  • itertools + generadores = pipelines con memoria constante

¿Terminaste de leer la teoría?

Marca este paso para registrar tu avance y saltar a las preguntas

02

Quiz de Comprobación

3 preguntas de comprensión rápida (requiere 70% para aprobar)

  1. 1

    ¿Qué es una función pura?

  2. 2

    ¿Qué hace functools.reduce(lambda a, b: a + b, [1, 2, 3, 4])?

  3. 3

    ¿Qué produce itertools.pairwise([1, 2, 3, 4])?

03

Laboratorio de Código Python

2 ejercicios interactivos ejecutados en tu navegador

EJERCICIO 01Python 3.12

map y filter juntos

Usa map y filter (o una combinación equivalente) para imprimir los cuadrados de los números impares del 1 al 10. Resultado: [1, 9, 25, 49, 81]

ejercicio_1.py
Tab: 4 spaces

Iniciando editor de código…

Ctrl/Cmd + Enter para ejecutar
EJERCICIO 02Python 3.12

Pipeline con reduce

Usa functools.reduce para calcular el producto de todos los números de la lista [2, 3, 4, 5] e imprimirlo. Resultado: 120

ejercicio_2.py
Tab: 4 spaces

Iniciando editor de código…

Ctrl/Cmd + Enter para ejecutar