Funções, métodos e objetos chamáveis carregam um contrato: parâmetros posicionais, argumentos nomeados, valores padrão, anotações e retorno. O módulo inspect permite ler esse contrato em runtime por meio de inspect.signature(). A API é usada por frameworks web, sistemas de injeção de dependência, CLIs, validadores, decorators, ferramentas de documentação e bibliotecas que adaptam chamadas dinamicamente.
Neste guia, você aprenderá a obter assinaturas, interpretar tipos de parâmetros, vincular argumentos com bind(), aplicar defaults, preservar metadados em decorators, modificar assinaturas e reconhecer os limites da introspecção.
Primeira assinatura
from inspect import signature
def criar_usuario(nome: str, idade: int = 18) -> dict[str, object]:
return {"nome": nome, "idade": idade}
sig = signature(criar_usuario)
print(sig)
O resultado representa a assinatura completa. Ele não é apenas texto: Signature possui objetos Parameter, anotação de retorno e métodos para validar chamadas.
Percorrendo parâmetros
for nome, parametro in sig.parameters.items():
print(nome)
print(parametro.kind)
print(parametro.default)
print(parametro.annotation)
parameters é um mapeamento ordenado. A ordem importa porque reproduz a declaração original e as regras de chamada do Python.
Tipos de Parameter
Cada parâmetro possui um kind. Os cinco tipos são:
POSITIONAL_ONLY: só pode ser enviado por posição.POSITIONAL_OR_KEYWORD: aceita posição ou nome.VAR_POSITIONAL: representa*args.KEYWORD_ONLY: aparece depois de*e exige nome.VAR_KEYWORD: representa**kwargs.
def exemplo(a, /, b, *args, c, **kwargs):
...
for p in signature(exemplo).parameters.values():
print(p.name, p.kind)
Frameworks que geram formulários ou comandos precisam respeitar essas categorias. Transformar tudo em argumento nomeado pode quebrar parâmetros position-only.
Valores ausentes
Quando um parâmetro não possui default ou anotação, a API usa inspect.Parameter.empty.
from inspect import Parameter
for p in sig.parameters.values():
if p.default is Parameter.empty:
print(p.name, "é obrigatório")
Não compare com None, pois None pode ser um default legítimo.
Anotação de retorno
from inspect import Signature
if sig.return_annotation is not Signature.empty:
print(sig.return_annotation)
A anotação pode ser uma classe, expressão genérica, string ou outro objeto. signature() não substitui typing.get_type_hints() quando referências futuras precisam ser resolvidas.
Validando chamadas com bind
argumentos = sig.bind("Ana", idade=30)
print(argumentos.arguments)
bind() aplica as mesmas regras de chamada da função e gera TypeError em caso de argumento ausente, duplicado ou inesperado. Isso é muito útil para adapters e sistemas de despacho.
Validação parcial
parcial = sig.bind_partial(nome="Ana")
bind_partial() permite omitir argumentos obrigatórios. Ele é apropriado para functools.partial, builders e configurações montadas em etapas. Não o use quando a chamada final precisa estar completa.
Aplicando defaults
vinculados = sig.bind("Ana")
vinculados.apply_defaults()
print(vinculados.arguments)
Antes de apply_defaults(), apenas argumentos enviados aparecem no mapeamento. Depois, parâmetros opcionais recebem seus padrões, *args vira tupla vazia e **kwargs vira dicionário vazio.
BoundArguments
O objeto retornado por bind mantém args, kwargs e arguments. Isso permite normalizar uma chamada e repassá-la:
resultado = criar_usuario(*vinculados.args, **vinculados.kwargs)
Ao alterar arguments, as propriedades derivadas refletem a mudança. Faça isso com cautela e valide tipos separadamente, pois bind verifica formato, não semântica.
Métodos e self
class Servico:
def executar(self, tarefa: str) -> None:
...
print(signature(Servico.executar))
print(signature(Servico().executar))
A assinatura do método não vinculado inclui self; a do método vinculado normalmente não. Frameworks precisam decidir se inspecionam a classe ou a instância.
Classes chamáveis
class Conversor:
def __call__(self, valor: str, *, estrito: bool = False) -> int:
return int(valor)
print(signature(Conversor()))
signature() pode inspecionar objetos que implementam __call__. Isso é útil em pipelines e dependências configuráveis.
Classes e construtores
class Usuario:
def __init__(self, nome: str, ativo: bool = True):
...
print(signature(Usuario))
Para classes, a API representa a forma de construção conforme __call__, __new__ e __init__. Metaclasses personalizadas podem alterar o resultado.
Decorators podem esconder a assinatura
def registrar(funcao):
def wrapper(*args, **kwargs):
print("chamada")
return funcao(*args, **kwargs)
return wrapper
Sem cuidados, a assinatura visível vira (*args, **kwargs). Use functools.wraps para definir __wrapped__ e preservar metadados:
from functools import wraps
def registrar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
return funcao(*args, **kwargs)
return wrapper
Por padrão, signature() segue a cadeia __wrapped__. O guia de decorators no Python explica esse padrão.
follow_wrapped
signature(funcao_decorada, follow_wrapped=False)
Defina follow_wrapped=False quando quiser inspecionar o wrapper real em vez da função original.
Assinaturas personalizadas
Objetos podem expor __signature__. Frameworks usam esse recurso para apresentar uma interface gerada dinamicamente. Contudo, o comportamento é uma convenção de introspecção; não muda automaticamente a aceitação real de argumentos.
Criando Parameter
from inspect import Parameter, Signature
parametro = Parameter(
"limite",
kind=Parameter.KEYWORD_ONLY,
default=100,
annotation=int,
)
nova_sig = Signature([parametro], return_annotation=list)
Objetos são imutáveis. Use construtores ou replace() para criar novas versões.
Modificando com replace
nova = sig.replace(return_annotation=dict[str, object])
Parameter.replace() altera nome, tipo, default ou anotação sem mutar o original. Respeite a ordem válida: parâmetros obrigatórios antes de opcionais e categorias na sequência aceita pelo Python.
Anotações em string
Com anotações adiadas, a assinatura pode conter strings. Em versões modernas, signature() possui opções relacionadas à avaliação e formatação, mas código portável deve separar introspecção estrutural da resolução de tipos.
from typing import get_type_hints
hints = get_type_hints(criar_usuario, include_extras=True)
Consulte get_type_hints no Python para referências futuras e namespaces.
Built-ins e extensões
Muitas funções implementadas em C oferecem metadados de assinatura, mas nem todas são introspectáveis. signature() pode lançar ValueError quando não existe assinatura e TypeError quando o objeto não é chamável.
try:
sig = signature(objeto)
except (TypeError, ValueError):
sig = None
Injeção de dependência
Um container pode percorrer parâmetros, resolver cada dependência pela anotação e chamar a função com valores montados. Entretanto, anotações não são validação. O container precisa tratar defaults, parâmetros variádicos, aliases e mensagens de erro.
Gerando uma CLI
Parâmetros obrigatórios podem virar argumentos posicionais; keyword-only podem virar opções; bool pode virar flag. Ainda assim, a assinatura não contém descrições amigáveis, regras de validação ou exemplos. Metadados extras podem vir de Annotated.
Cache
Inspecionar repetidamente funções em rotas quentes pode ter custo. Como funções e suas assinaturas normalmente são estáveis, frameworks podem manter cache por objeto. Invalide-o se decorators ou plugins modificarem __signature__ dinamicamente.
Segurança
A inspeção estrutural não executa a função, mas resolver anotações pode avaliar nomes. Não carregue módulos ou hints de plugins não confiáveis sem isolamento. Também evite expor defaults que contenham segredos em documentação ou logs.
Erros comuns
- Comparar default com None: use
Parameter.empty. - Ignorar parameter kind: position-only e keyword-only possuem regras reais.
- Confundir bind com validação de tipos: ele verifica apenas a forma da chamada.
- Perder assinatura em decorators: aplique
functools.wraps. - Assumir suporte em todo built-in: trate TypeError e ValueError.
- Modificar __signature__ sem adaptar runtime: introspecção e comportamento podem divergir.
Exemplo completo: executor configurável
from inspect import Parameter, signature
from typing import get_type_hints
def executar(funcao, valores: dict[str, object]):
sig = signature(funcao)
hints = get_type_hints(funcao, include_extras=True)
kwargs = {}
for nome, parametro in sig.parameters.items():
if parametro.kind in {
Parameter.VAR_POSITIONAL,
Parameter.VAR_KEYWORD,
}:
continue
if nome in valores:
kwargs[nome] = valores[nome]
elif parametro.default is Parameter.empty:
raise ValueError(f"valor ausente: {nome}")
vinculados = sig.bind(**kwargs)
vinculados.apply_defaults()
return funcao(*vinculados.args, **vinculados.kwargs)
O exemplo normaliza a chamada e detecta ausências, mas um executor real ainda precisa validar tipos, tratar position-only e construir mensagens específicas.
Conclusão
inspect.signature() transforma o contrato de um callable em objetos estruturados e confiáveis. Com Signature, Parameter e BoundArguments, você pode validar a forma de chamadas, gerar interfaces e preservar decorators sem analisar código-fonte manualmente.
A documentação oficial de inspect.signature detalha a API. Use-a com get_type_hints(), respeite parameter kinds e mantenha introspecção separada da validação de tipos e da execução.







