O módulo inspect permite observar funções, métodos, classes e outros objetos em tempo de execução. Entre seus recursos mais úteis está inspect.signature(), que devolve uma representação estruturada da assinatura de uma função. A partir dessa assinatura, o método bind() associa argumentos posicionais e nomeados aos parâmetros corretos. Essa combinação é valiosa para validação, depuração, criação de decorators, frameworks, sistemas de injeção de dependência e ferramentas que precisam chamar funções dinamicamente sem perder as regras normais do Python.
O que é uma assinatura de função
A assinatura descreve quais parâmetros uma função aceita, a ordem deles, valores padrão, parâmetros somente posicionais, parâmetros somente nomeados, *args e **kwargs. Ao executar inspect.signature(funcao), o Python retorna um objeto Signature. Esse objeto não é apenas texto: ele oferece métodos e estruturas que permitem analisar e validar chamadas.
from inspect import signature
def calcular_total(preco, quantidade=1, *, desconto=0):
return preco * quantidade * (1 - desconto)
sig = signature(calcular_total)
print(sig)
# (preco, quantidade=1, *, desconto=0)O resultado mostra exatamente como a função deve ser usada. Isso é útil em documentação automática, interfaces de linha de comando, testes e metaprogramação. Para revisar fundamentos relacionados, veja também os conteúdos sobre funções em Python, args e kwargs, decorators e type hints.
Como funciona o método bind
O método bind() recebe argumentos da mesma maneira que a função receberia. Ele verifica se a chamada é compatível com a assinatura e devolve um objeto BoundArguments. Esse objeto contém o mapeamento entre o nome de cada parâmetro e o valor fornecido.
ligados = sig.bind(100, 2, desconto=0.1)
print(ligados.arguments)
# {'preco': 100, 'quantidade': 2, 'desconto': 0.1}Essa validação segue as regras reais do Python. Se faltar um argumento obrigatório, se houver um argumento inesperado ou se um parâmetro receber valor duas vezes, bind() levanta TypeError. Isso evita reproduzir manualmente uma lógica complexa de validação.
bind versus bind_partial
bind() exige que todos os parâmetros obrigatórios estejam presentes. Já bind_partial() aceita uma associação incompleta. Ele é útil quando os argumentos serão preenchidos em etapas, como em formulários, pipelines, sistemas de configuração ou implementações semelhantes a functools.partial.
parcial = sig.bind_partial(preco=150)
print(parcial.arguments)
# {'preco': 150}Use bind() para validar uma chamada que está pronta para execução. Use bind_partial() quando o objetivo for representar apenas parte da chamada. Misturar os dois pode esconder erros: uma chamada parcial não garante que a função possa ser executada naquele momento.
Aplicando valores padrão
O objeto BoundArguments inicialmente inclui apenas os valores explicitamente fornecidos. Para preencher parâmetros opcionais com seus valores padrão, utilize apply_defaults().
ligados = sig.bind(100)
ligados.apply_defaults()
print(ligados.arguments)
# {'preco': 100, 'quantidade': 1, 'desconto': 0}Esse comportamento é especialmente útil em logs, auditorias e serialização de configurações, porque produz uma visão completa da chamada. Parâmetros variádicos também recebem valores vazios apropriados: tupla vazia para *args e dicionário vazio para **kwargs.
Executando a função com BoundArguments
O objeto oferece as propriedades args e kwargs, que reorganizam os valores de acordo com a assinatura original. Assim, depois de validar ou modificar os dados, você pode chamar a função com segurança.
ligados = sig.bind(100, desconto=0.2)
ligados.apply_defaults()
resultado = calcular_total(*ligados.args, **ligados.kwargs)
print(resultado)Essa técnica é útil em decorators genéricos. Um decorator pode inspecionar a assinatura, normalizar valores, registrar parâmetros e então chamar a função sem precisar conhecer previamente os nomes dos argumentos.
Exemplo em um decorator de validação
from inspect import signature
from functools import wraps
def validar_positivos(func):
sig = signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
ligados = sig.bind(*args, **kwargs)
ligados.apply_defaults()
for nome, valor in ligados.arguments.items():
if isinstance(valor, (int, float)) and valor < 0:
raise ValueError(f'{nome} não pode ser negativo')
return func(*ligados.args, **ligados.kwargs)
return wrapperEsse decorator funciona com diferentes funções porque delega ao próprio Python o trabalho de associar valores aos parâmetros. Em projetos reais, a validação deve considerar tipos esperados, valores opcionais e regras específicas do domínio.
Parâmetros especiais
Signature.bind() respeita parâmetros somente posicionais, marcados antes de /, e parâmetros somente nomeados, colocados depois de *. Também agrupa argumentos excedentes em *args e palavras-chave adicionais em **kwargs. Essa fidelidade torna o recurso melhor do que soluções baseadas apenas em contagem de argumentos.
Ao construir APIs dinâmicas, preserve as mensagens de erro originais sempre que possível. Elas são familiares para desenvolvedores Python e ajudam a identificar rapidamente chamadas incorretas. Evite capturar todo TypeError sem distinguir se o erro ocorreu durante o binding ou dentro do corpo da função.
Casos de uso práticos
Frameworks web podem mapear parâmetros de rota para argumentos de uma função. Sistemas de tarefas podem converter dados JSON em chamadas. Bibliotecas de teste podem preencher fixtures automaticamente. Ferramentas de linha de comando podem gerar opções a partir dos parâmetros. Sistemas de plugins podem verificar se uma função segue um contrato. Em todos esses casos, bind() reduz código duplicado e mantém compatibilidade com as regras da linguagem.
Outro uso importante é a criação de logs estruturados. Em vez de registrar apenas uma tupla de argumentos posicionais, o sistema pode registrar nomes significativos. Isso melhora observabilidade, mas dados sensíveis como senhas, tokens e chaves devem ser removidos ou mascarados antes do registro.
Cuidados com desempenho e segurança
Inspecionar assinaturas possui custo. Em caminhos executados muitas vezes, armazene o objeto Signature em cache, principalmente em decorators. Também evite usar dados externos para chamar qualquer função arbitrária. O binding valida o formato da chamada, mas não torna a execução segura. A função permitida deve ser escolhida por uma lista controlada.
Nem todos os objetos expõem assinaturas completas. Algumas funções implementadas em extensões podem não fornecer metadados suficientes e gerar ValueError ou TypeError em signature(). Trate esse cenário quando sua ferramenta aceitar objetos de origens variadas.
Boas práticas
Crie a assinatura uma vez e reutilize-a. Use bind() para chamadas completas e bind_partial() apenas quando a incompletude for intencional. Chame apply_defaults() quando precisar de uma representação completa. Preserve args e kwargs do objeto para executar a função na ordem correta. Teste parâmetros posicionais, nomeados, padrões, variádicos e casos inválidos.
A documentação oficial de inspect no Python detalha Signature, Parameter e BoundArguments. A especificação de chamadas e definições de funções também pode ser consultada na referência da linguagem Python.
Conclusão
inspect.signature().bind() transforma uma chamada em uma estrutura clara, validada e fácil de manipular. Ele evita validações manuais frágeis e permite criar ferramentas genéricas que continuam respeitando os detalhes da linguagem. Ao combinar bind(), apply_defaults(), args e kwargs, você obtém uma base sólida para decorators, frameworks, plugins, testes e sistemas de automação.







