map, filter, reduce y functools: transformar datos funcionalmente
map, filter y reduce son las herramientas clásicas de programación funcional. Python las incluye, aunque a menudo las comprehensions son más Pythonicas. Conocerlas es esencial para entender código legado y librerías.
map() con funciones y lambda
map(función, iterable) aplica una función a cada elemento de un iterable y devuelve un iterador lazy. Debes convertirlo a lista si quieres verlo completo.
# map con una función nombrada
def al_cuadrado(n):
return n ** 2
numeros = [1, 2, 3, 4, 5]
resultado = list(map(al_cuadrado, numeros))
print(resultado) # [1, 4, 9, 16, 25]
# map con lambda (función anónima en línea)
dobles = list(map(lambda x: x * 2, numeros))
print(dobles) # [2, 4, 6, 8, 10]
# map sobre strings
nombres = ["ana", "carlos", "beatriz"]
mayusculas = list(map(str.upper, nombres))
print(mayusculas) # ['ANA', 'CARLOS', 'BEATRIZ']
# map con múltiples iterables
a = [1, 2, 3]
b = [10, 20, 30]
sumas = list(map(lambda x, y: x + y, a, b))
print(sumas) # [11, 22, 33][1, 4, 9, 16, 25]
[2, 4, 6, 8, 10]
['ANA', 'CARLOS', 'BEATRIZ']
[11, 22, 33]map() es lazy: no evalúa nada hasta que iteras. Envuelve con list() cuando necesitas el resultado completo, o itera directamente en un for loop para ahorrar memoria.
filter() para subconjuntos
filter(función, iterable) devuelve un iterador con los elementos para los que la función retorna True. Si pasas None como función, filtra los valores falsy.
# Filtrar números pares
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares = list(filter(lambda n: n % 2 == 0, numeros))
print(pares) # [2, 4, 6, 8, 10]
# Filtrar strings no vacíos
datos = ["python", "", "es", None, "genial", ""]
# filter con None elimina los valores falsy
validos = list(filter(None, datos))
print(validos) # ['python', 'es', 'genial']
# Filtrar objetos por atributo
usuarios = [
{"nombre": "Ana", "activo": True},
{"nombre": "Bob", "activo": False},
{"nombre": "Carlos", "activo": True},
]
activos = list(filter(lambda u: u["activo"], usuarios))
print([u["nombre"] for u in activos]) # ['Ana', 'Carlos'][2, 4, 6, 8, 10]
['python', 'es', 'genial']
['Ana', 'Carlos']functools.reduce() para acumulación
reduce(función, iterable) aplica una función acumulativa de izquierda a derecha, reduciendo el iterable a un solo valor. En Python 3 está en functools.
from functools import reduce
# Sumar todos los elementos
numeros = [1, 2, 3, 4, 5]
total = reduce(lambda acc, n: acc + n, numeros)
print(total) # 15
# Producto de todos los elementos
producto = reduce(lambda acc, n: acc * n, numeros)
print(producto) # 120
# Encontrar el máximo manualmente
maximo = reduce(lambda a, b: a if a > b else b, numeros)
print(maximo) # 5
# Con valor inicial (segundo parámetro de reduce)
suma_con_base = reduce(lambda acc, n: acc + n, numeros, 100)
print(suma_con_base) # 115 (100 + 1 + 2 + 3 + 4 + 5)15
120
5
115Para operaciones comunes como suma, máximo o mínimo, Python ya tiene sum(), max() y min(). Usa reduce() solo cuando necesitas una operación de acumulación personalizada que no tiene built-in equivalente.
Comparación: comprehension vs map/filter
En Python moderno, las comprehensions suelen ser más legibles que map/filter. Conoce ambas formas — el código legado usa mucho map/filter.
| map / filter | List comprehension |
|---|---|
| list(map(lambda x: x**2, nums)) | [x**2 for x in nums] |
| list(filter(lambda x: x>0, nums)) | [x for x in nums if x>0] |
| list(map(f, filter(g, nums))) | [f(x) for x in nums if g(x)] |
numeros = [-3, -1, 0, 2, 4, 7]
# Opción A: map + filter anidados (menos legible)
resultado_a = list(map(lambda n: n ** 2, filter(lambda n: n > 0, numeros)))
# Opción B: list comprehension (más legible)
resultado_b = [n ** 2 for n in numeros if n > 0]
print(resultado_a) # [4, 16, 49]
print(resultado_b) # [4, 16, 49]
print(resultado_a == resultado_b) # True[4, 16, 49]
[4, 16, 49]
Truefunctools.partial y functools.lru_cache
functools tiene más herramientas útiles: partial para fijar argumentos y lru_cache para memorizar resultados de funciones puras costosas.
from functools import partial, lru_cache
# partial: crea una función con argumentos pre-rellenados
def potencia(base, exponente):
return base ** exponente
al_cuadrado = partial(potencia, exponente=2)
al_cubo = partial(potencia, exponente=3)
print(al_cuadrado(5)) # 25
print(al_cubo(3)) # 27
# Útil con map para evitar lambdas complejas
numeros = [1, 2, 3, 4, 5]
print(list(map(al_cuadrado, numeros))) # [1, 4, 9, 16, 25]
# lru_cache: memoiza una función (guarda resultados ya calculados)
@lru_cache(maxsize=None)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(35)) # 9227465 — rápido gracias al caché
print(fibonacci.cache_info()) # CacheInfo(hits=33, misses=36, ...)25
27
[1, 4, 9, 16, 25]
9227465
CacheInfo(hits=33, misses=36, maxsize=None, currsize=36)