ParamSpec em Python: Decorators com Tipagem resolve um problema recorrente em projetos Python: Encaminhe a assinatura completa de uma callable por uma função de ordem superior. Este guia mostra o mecanismo, um exemplo executável e os limites que evitam uma implementação frágil.
Conceito e caso de uso
ParamSpec captura parâmetros posicionais e nomeados; TypeVar captura o retorno. Juntos, preservam a relação entre função recebida e função devolvida.
Para revisar os fundamentos relacionados, consulte também o guia de type hints. A integração fica mais simples quando cada função recebe dependências e dados explicitamente, em vez de depender de estado global.
Exemplo prático
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
Use functools.wraps para metadados runtime e Callable[P, R] para o contrato estático. Concatenate serve quando o wrapper adiciona um argumento inicial.
Decisões importantes
A escolha correta depende do contrato público, do volume e do comportamento em caso de falha.
Considere como a solução se comporta sob concorrência, entradas vazias e falhas parciais. Documente qualquer limite que afete consumidores e mantenha nomes que expressem a intenção.
Erros comuns
O exemplo mínimo não substitui limites, tratamento de erros e observabilidade. Callable[..., R] perde a verificação dos argumentos. ParamSpec não copia automaticamente nome, docstring ou assinatura observável sem wraps.
Evite capturar exceções sem contexto ou retornar resultados parciais como se fossem completos. Uma falha explícita costuma ser mais segura que dados silenciosamente incorretos.
Como validar
Valide o comportamento, não apenas a linha feliz. Rode o type checker sobre funções com assinaturas diferentes e verifique autocomplete, argumentos inválidos e tipo de retorno.