functools
functools proporciona funciones de orden superior para caché, aplicación parcial, reducción y genéricos de envío único (single-dispatch), bloques de construcción comunes en decoradores y APIs.
Receta
from functools import lru_cache, partial
@lru_cache(maxsize=128)
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
double = partial(pow, 2)Cuándo usar esto:
- Para memoizar funciones puras costosas
- Para currificación de configuración en callbacks
- Para escribir decoradores que preserven
__name__ - Para comportamiento de función específico de tipo sin cadenas
if isinstance
Ejemplo de Trabajo
from functools import lru_cache, partial, reduce, singledispatch, wraps
def logger(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print(f"llamada {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
@logger
@lru_cache(maxsize=32)
def tokenize(text: str) -> tuple[str, ...]:
return tuple(text.lower().split())
@singledispatch
def serialize(value) -> str:
return str(value)
@serialize.register(int)
def _(value: int) -> str:
return f"i:{value}"
def product(nums: list[int]) -> int:
return reduce(lambda a, b: a * b, nums, 1)
if __name__ == "__main__":
print(tokenize("Hello World"))
print(serialize(42))
print(product([2, 3, 4]))
add5 = partial(lambda a, b: a + b, 5)
print(add5(10))Lo que esto demuestra:
@wrapscopia metadatos al wrapper para introspección/depuraciónlru_cacherequiere argumentos hasheablessingledispatchregistra manejadores por tiporeducepliega un iterable con una función binaria
Análisis Profundo
Cómo Funciona
- lru_cache - Diccionario indexado por argumentos; descarta el menos usado recientemente al alcanzar
maxsize. - cache_clear - Invalida los resultados memoizados cuando las entradas cambian externamente.
- partial -
partial(func, *args, **kwargs)fija los argumentos iniciales. - singledispatch - Despacha según el tipo del primer argumento; se registra con un decorador.
- total_ordering - Genera métodos de comparación a partir de
__eq__y un método de ordenamiento.
Orden de Pila de Decoradores
@outer
@inner
def f(): ...
# equivale a f = outer(inner(f)) - inner se aplica primeroNotas de Python
from functools import cached_property # 3.8+
class Config:
@cached_property
def heavy(self) -> dict:
return load()Trampas Comunes
- lru_cache con argumentos mutables - Las listas no son hasheables. Solución: Usar tuplas como argumentos o deshabilitar la caché.
- Caché de funciones impuras - Lecturas obsoletas cuando el estado oculto cambia. Solución: Usar
cache_clearo no usar caché. maxsize=Nonesin límites en la caché - Crecimiento de memoria en un espacio de claves infinito. Solución: Establecer unmaxsize.partialoscurece la firma - La inspección es menos clara. Solución: Usar un wrapper con parámetros explícitos para APIs públicas.singledispatchno es para múltiples argumentos - Solo considera el tipo del primer argumento. Solución: Usar bibliotecas de multimétodos o lógica manual.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| caché manual con diccionario | Evicción personalizada | Cuando lru_cache es suficiente |
@cache (3.9+) | Espacio de claves pequeño sin límites | Se necesita evicción LRU |
match/case para despachar | Pocos tipos en línea | Extensión abierta a través de módulos |
itertools.accumulate | Fold (plegado) en ejecución sin importar reduce | Solo se necesita un valor final único |
Preguntas Frecuentes
¿`lru_cache` vs `cache`?
functools.cache es una caché simple sin límites, similar a LRU (3.9+). lru_cache controla el tamaño y las estadísticas.
¿`lru_cache` es seguro para hilos (thread-safe)?
CPython protege el diccionario de caché para operaciones individuales; aún así, coordina si la función subyacente no es segura para hilos.
¿cuándo usar `partial`?
Para unir configuraciones en callbacks - partial(send_email, smtp=cfg) en bucles.
¿es necesario `wraps`?
Sí, para decoradores públicos - preserva __name__, __doc__, anotaciones para herramientas.
¿sigue siendo `reduce` "pythónico"?
Aceptable para folds funcionales; a menudo es más claro total = 1; for x in nums: total *= x para principiantes.
¿extensión de `singledispatch`?
Registrar en otros módulos con el mismo nombre de función importado - el orden importa.
¿`cached_property` vs `lru_cache`?
cached_property almacena en la instancia una vez; lru_cache en los argumentos de la función globalmente.
¿advertencia de `total_ordering`?
Se debe definir __eq__ y un método de comparación; los demás se derivan - puede ser más lento que hacerlo manualmente.
¿`cache_info`?
fib.cache_info() muestra aciertos/fallos/tamaño máximo/tamaño actual para ajustar.
¿`functools` en la ruta crítica (hot path)?
lru_cache ayuda con llamadas puras costosas; partial tiene un costo insignificante.
Relacionado
- Escribir Decoradores - patrón
wraps - Closures y Captura de Ámbito - patrón de fábrica
- Sobrecargas y Tipos Llamables - sobrecargas de tipado
- itertools -
accumulatevsreduce
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.