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:
- Para las mismas entradas devuelve siempre la misma salida
- 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,
mapyfilterson 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íneapartial: 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/filtertransforman y filtran; en Python la comprehension suele ganar en legibilidadreducepliega a un valor;sum/max/minson los casos comunes ya resueltos@lru_cachepara memoización;partialpara especializar funcionesitertools+ generadores = pipelines con memoria constante