Pregunta 1¿Cuál es la forma más concisa de construir el producto cartesiano de [1, 2, 3] y [A, B]?
itertools y functools — Recetas de iteración y composición de funciones
Aprende con ejemplos las recetas de iteración combinations / product / chain / accumulate, fijar argumentos con partial, y acelerar recursión memoizando con @lru_cache.
itertools es un conjunto de funciones que construyen nuevos iterables a partir de otros existentes (combinaciones, concatenación, acumulación), y functools es un conjunto de funciones que transforman la propia función (fijar argumentos, memoización). Ambos pueden reemplazar patrones de bucle for + seguimiento de estado en una sola línea, reduciendo drásticamente tu código.
Iterables — valores sobre los que puedes iterar
La mayoría de funciones de itertools toman un "iterable" como entrada y devuelven un nuevo iterable, así que vale la pena fijar primero qué es un iterable. Un iterable (lo que puedes escribir a la derecha de for x in ___:) es cualquier cosa que puedas pasar a un bucle for, a list(...), a sum(...) o a map(...). list / tuple / str / dict / set / range, además de los generadores que tú escribes (funciones que usan yield), todos se comportan como iterables en Python.
list / tuple / str / dict / set / range son iterables, y también lo son los generadores propios construidos con yield.itertools — kit de combinación e iteración
itertools es un módulo que reúne funciones que construyen nuevos iterables a partir de otros existentes. Las cuatro que más usarás son combinations (combinaciones sin orden), product (producto cartesiano sobre varios conjuntos), chain (concatenar iterables en uno) y accumulate (totales o productos acumulados). Conocer estas cuatro te permite reescribir dobles bucles for y bucles con estado mucho más limpiamente.
list(...) para materializar.| Función | Entrada → Salida | Ejemplo |
|---|---|---|
| combinations(it, k) | Combinaciones de k elementos (sin orden) | [A,B,C] → (A,B), (A,C), (B,C) |
| permutations(it, k) | Permutaciones de k elementos (con orden) | [A,B] → (A,B), (B,A) |
| product(*its) | Producto cartesiano entre conjuntos | [S,M] × [red,blue] → 4 combos |
| chain(*its) | Concatenar varios iterables | [1,2] + [3,4] → [1,2,3,4] |
| accumulate(it) | Acumular desde la izquierda (suma por defecto) | [10,20,30] → [10, 30, 60] |
functools.partial — fija argumentos por adelantado
functools.partial construye una nueva función con algunos argumentos ya fijados. Cuando necesitas pasar una función con argumentos fijados a un callback o a una función de orden superior, esto te ahorra escribir wrappers de una línea como def wrapper(...): .... En cualquier sitio donde llames a la misma función repetidamente con ligeras variaciones de argumentos, partial reduce el ruido del wrapper y hace el código más legible.
from functools import partial
def format_with_unit(price, unit):
return f"{price}{unit}"
# Construye una nueva función con la unidad "USD" prefijada
to_usd = partial(format_with_unit, unit="USD")
# Aplica a precios individuales — solo hay que pasar price ahora
print(to_usd(100)) # 100USD
print(to_usd(200)) # 200USD
print(to_usd(300)) # 300USD
exp=2 en power(base, exp) y obtienes una función square que solo necesita base.functools.lru_cache — cachea resultados con memoización
"Quiero reutilizar el resultado de una función costosa que se llama repetidamente con los mismos argumentos" — surge siempre que las salidas de una función vienen determinadas por sus entradas y la velocidad importa. Hacerlo a mano significa gestionar tu propio dict de caché, pero @lru_cache lo hace en una sola línea con un decorador.
@lru_cache es un decorador que cachea los valores de retorno de una función. Cuando se llama de nuevo con los mismos argumentos, devuelve el valor cacheado directamente, saltándose el recálculo. LRU (Least Recently Used — descarta las entradas no usadas durante más tiempo) descarta entradas antiguas de la caché así que puedes limitar el uso de memoria con algo como maxsize=128.
No lo apliques a funciones con efectos secundarios
@lru_cache asume "mismos argumentos → mismo valor de retorno". Aplícalo a funciones con efectos secundarios (leer archivos / escribir en una BD / devolver la hora actual) o cuyo resultado varíe para las mismas entradas, y tendrás un bug donde el primer resultado vuelve para siempre. Aplícalo solo a funciones puras (misma entrada → misma salida, sin efectos secundarios).
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Qué devuelve partial(f, x=10)?
Pregunta 3¿Por qué @lru_cache acelera un Fibonacci recursivo ingenuo?