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 y filter
numeros = [1, 2, 3, 4, 5, 6]
cuadrados = list(map(lambda n: n ** 2, numeros)) # transformar
pares = list(filter(lambda n: n % 2 == 0, numeros)) # filtrar
print(cuadrados) # [1, 4, 9, 16, 25, 36]
print(pares) # [2, 4, 6]
map transforma cada elemento; filter conserva los que cumplen. Ambos devuelven iteradores perezosos — por eso los envuelves en list() para verlos.
Verdad incómoda: en Python, las comprehensions suelen preferirse a map/filter por legibilidad:
cuadrados = [n ** 2 for n in numeros] # equivalente a map
pares = [n for n in numeros if n % 2 == 0] # equivalente a filter
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