ParamSpec en Python: Decorators con Tipado resuelve un problema frecuente en proyectos Python: Reenvía la firma completa de una callable mediante una función de orden superior. Esta guía explica el mecanismo, presenta un ejemplo ejecutable y marca los límites que evitan una implementación frágil.
Concepto y caso de uso
ParamSpec captura parámetros posicionales y nombrados; TypeVar captura el retorno. Juntos conservan la relación entre función recibida y devuelta.
Para repasar los fundamentos relacionados, consulta también la guía de type hints. La integración es más simple cuando cada función recibe dependencias y datos explícitamente en lugar de depender de estado global.
Ejemplo práctico
from collections.abc import Callable
from functools import wraps
from typing import ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def traced(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(func.__name__)
return func(*args, **kwargs)
return wrapper
Usa functools.wraps para metadatos runtime y Callable[P, R] para el contrato estático. Concatenate sirve si se añade un argumento inicial.
Decisiones importantes
La elección correcta depende del contrato público, del volumen y del comportamiento ante fallos.
Considera concurrencia, entradas vacías y fallos parciales. Documenta cada límite que afecte a consumidores y usa nombres que expresen intención.
Errores frecuentes
El ejemplo mínimo no sustituye límites, manejo de errores y observabilidad. Callable[..., R] pierde la comprobación de argumentos. ParamSpec no conserva nombre, documentación ni introspección por sí solo.
Evita capturar excepciones sin contexto o devolver resultados parciales como si fueran completos. Un fallo explícito suele ser más seguro que datos silenciosamente incorrectos.
Cómo validar
Valida el comportamiento y no solo el camino feliz. Ejecuta el type checker con firmas distintas y comprueba autocompletado, argumentos rechazados y retorno inferido.