Frameworks, depuradores, sistemas de plugins, geradores de documentação e ferramentas de teste frequentemente precisam descobrir como um objeto Python foi definido. Eles verificam se algo é função, classe ou coroutine, listam membros, leem assinaturas, encontram código-fonte e analisam a pilha de execução. O módulo inspect no Python reúne essas operações de introspecção em uma API da biblioteca padrão.
Neste guia, você aprenderá a usar getmembers(), os predicados is*, signature(), Signature.bind(), getsource(), unwrap(), getattr_static() e funções de frames. O conteúdo complementa nossos artigos sobre descriptors em Python, singledispatch, cópias de objetos, vazamentos de memória e contextvars.
O que é introspecção
Introspecção é a capacidade de um programa examinar objetos durante a execução. Em Python, funções, classes, módulos e métodos carregam metadados como nome, módulo, documentação, annotations e referências ao código compilado.
def calcular_total(valor: float, taxa: float = 0.1) -> float:
"""Calcula um total com taxa."""
return valor * (1 + taxa)
print(calcular_total.__name__)
print(calcular_total.__doc__)
print(calcular_total.__annotations__)inspect fornece uma interface mais uniforme e ferramentas que funcionam com vários tipos de objetos.
Identificando o tipo de objeto
Predicados como isfunction(), ismethod(), isclass() e ismodule() deixam a intenção clara.
import inspect
class Servico:
def executar(self):
return "ok"
servico = Servico()
print(inspect.isclass(Servico))
print(inspect.isfunction(Servico.executar))
print(inspect.ismethod(servico.executar))
print(inspect.isroutine(servico.executar))Acesso pela classe devolve a função definida; acesso pela instância produz um método vinculado. Essa diferença é importante em registradores e frameworks de injeção de dependência.
Generators, coroutines e async generators
O módulo distingue funções que criam objetos assíncronos dos objetos já criados.
import inspect
async def buscar():
return 42
async def eventos():
yield "início"
def numeros():
yield 1
print(inspect.iscoroutinefunction(buscar))
print(inspect.isasyncgenfunction(eventos))
print(inspect.isgeneratorfunction(numeros))Depois de chamar, use iscoroutine(), isasyncgen(), isgenerator() ou isawaitable(). Feche ou aguarde objetos criados nos testes para evitar warnings.
Listando membros com getmembers()
getmembers() devolve pares (nome, valor) ordenados pelo nome.
import inspect
class Produto:
categoria = "geral"
def __init__(self, nome):
self.nome = nome
def resumo(self):
return self.nome
for nome, valor in inspect.getmembers(Produto):
if not nome.startswith("__"):
print(nome, valor)O segundo argumento pode ser um predicado:
metodos = inspect.getmembers(Produto, inspect.isfunction)
print([nome for nome, _ in metodos])Essa combinação é útil em descoberta de handlers, comandos e testes.
Cuidado: getmembers() pode executar código
A busca normal de atributos aciona descriptors, property, __getattr__() e __getattribute__(). Portanto, inspecionar um objeto pode executar lógica.
class Exemplo:
@property
def perigoso(self):
print("property executada")
return 10
inspect.getmembers(Exemplo())Em objetos não confiáveis ou ferramentas de documentação, isso pode causar efeitos colaterais.
Inspeção passiva com getmembers_static()
Desde Python 3.11, getmembers_static() evita a resolução dinâmica de atributos.
membros = inspect.getmembers_static(Exemplo())
for nome, valor in membros:
if nome == "perigoso":
print(valor) # descriptor property, sem executar getterO resultado pode conter o próprio descriptor em vez do valor e pode omitir atributos criados dinamicamente. A escolha depende do objetivo: comportamento real ou estrutura estática.
getattr_static()
getattr_static() recupera um atributo sem executar o protocolo de descriptors, __getattr__ ou __getattribute__.
descriptor = inspect.getattr_static(Exemplo, "perigoso")
print(type(descriptor))Essa função é indicada para analisadores e depuradores. Resolver manualmente um descriptor ainda pode executar código, então trate objetos arbitrários com cautela.
Documentação com getdoc()
getdoc() limpa indentação e pode herdar documentação de classes, métodos, propriedades e descriptors quando a subclasse não define uma nova docstring.
import inspect
print(inspect.getdoc(calcular_total))cleandoc() também pode ser usado diretamente para normalizar uma string de documentação.
Localizando arquivo e módulo
Funções como getfile(), getsourcefile() e getmodule() ajudam ferramentas de desenvolvimento.
print(inspect.getfile(calcular_total))
print(inspect.getsourcefile(calcular_total))
print(inspect.getmodule(calcular_total))Built-ins e extensões em C podem não possuir arquivo-fonte Python. Capture TypeError e aceite None quando a origem não estiver disponível.
Recuperando código-fonte
getsource() devolve o trecho textual associado a uma função, classe, método, módulo, frame ou objeto de código.
try:
fonte = inspect.getsource(calcular_total)
print(fonte)
except (OSError, TypeError):
print("Código-fonte indisponível")A documentação oficial de inspect informa que OSError ocorre quando a fonte não pode ser recuperada e TypeError em vários objetos built-in. Funções criadas no console, por exec() ou em ambientes empacotados também podem não ter fonte acessível.
getsourcelines() e comentários
getsourcelines() retorna uma lista de linhas e a linha inicial no arquivo.
linhas, inicio = inspect.getsourcelines(calcular_total)
print(inicio)
print("".join(linhas))getcomments() procura comentários imediatamente anteriores à definição. Não dependa deles como metadados estáveis; prefira docstrings e estruturas explícitas.
Assinaturas com signature()
signature() é a API recomendada para descobrir parâmetros de um callable.
from inspect import signature
sig = signature(calcular_total)
print(sig)
print(sig.return_annotation)
for nome, parametro in sig.parameters.items():
print(nome, parametro.kind, parametro.default, parametro.annotation)Ela entende funções, classes, métodos, functools.partial e muitos objetos callable.
Tipos de Parameter
Cada parâmetro possui um kind:
POSITIONAL_ONLYpara argumentos antes de/;POSITIONAL_OR_KEYWORDpara o caso comum;VAR_POSITIONALpara*args;KEYWORD_ONLYpara parâmetros depois de*;VAR_KEYWORDpara**kwargs.
def exemplo(a, /, b=2, *args, c, **kwargs):
pass
for param in inspect.signature(exemplo).parameters.values():
print(param.name, param.kind.description)Frameworks podem usar essas informações para construir formulários, validadores e chamadas adaptadas.
Ausência versus valor None
Parameter.empty significa que não há default ou annotation. Isso é diferente de um default explicitamente igual a None.
param = inspect.signature(calcular_total).parameters["taxa"]
if param.default is inspect.Parameter.empty:
print("Obrigatório")Use comparação por identidade com o marcador.
Validando chamadas com Signature.bind()
bind() associa argumentos aos parâmetros como uma chamada real e lança TypeError em caso de incompatibilidade.
sig = inspect.signature(calcular_total)
try:
ligados = sig.bind(100, taxa=0.2)
print(ligados.arguments)
except TypeError as erro:
print("Argumentos inválidos:", erro)Isso permite validar plugins ou rotas antes da execução.
bind_partial() e defaults
bind_partial() permite omitir parâmetros obrigatórios, imitando functools.partial().
parcial = sig.bind_partial(taxa=0.15)
print(parcial.arguments)BoundArguments.apply_defaults() inclui defaults, uma tupla vazia para *args e um dicionário vazio para **kwargs.
ligados = sig.bind(100)
ligados.apply_defaults()
print(ligados.arguments)Chamando com BoundArguments
As propriedades args e kwargs permitem executar o callable depois de validar ou transformar valores.
ligados = sig.bind("100", taxa="0.2")
ligados.arguments["valor"] = float(ligados.arguments["valor"])
ligados.arguments["taxa"] = float(ligados.arguments["taxa"])
resultado = calcular_total(*ligados.args, **ligados.kwargs)Não use annotations como conversores automaticamente sem uma política. Annotations podem ser objetos arbitrários.
Annotations e riscos de avaliação
signature() pode trabalhar com annotations como strings. Os parâmetros eval_str e o formato de annotations controlam a resolução. Avaliar strings pode executar código arbitrário.
Para introspecção de conteúdo não confiável, mantenha eval_str=False e prefira o formato textual. No Python 3.14, annotation_format integra-se a annotationlib.Format.
Decorators e __wrapped__
Decorators podem esconder o nome, documentação e assinatura originais. A função functools.wraps() copia metadados e cria __wrapped__.
from functools import wraps
def registrar(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Chamando", func.__name__)
return func(*args, **kwargs)
return wrapper
@registrar
def somar(a: int, b: int = 0) -> int:
return a + b
print(inspect.signature(somar))A documentação oficial de functools explica que wraps preserva metadados e permite introspecção do objeto original.
unwrap()
inspect.unwrap() segue a cadeia de __wrapped__ até a função original.
original = inspect.unwrap(somar)
print(original.__name__)Ele detecta ciclos e lança ValueError. O parâmetro stop permite interromper em um wrapper específico.
follow_wrapped
signature() segue wrappers por padrão. Use follow_wrapped=False para analisar a assinatura real do wrapper.
print(inspect.signature(somar, follow_wrapped=True))
print(inspect.signature(somar, follow_wrapped=False))Essa diferença ajuda a depurar decorators que adicionam comportamento ou argumentos.
Modificando Signature e Parameter
Esses objetos são imutáveis. Use replace() ou copy.replace() para criar versões modificadas.
sig = inspect.signature(somar)
nova = sig.replace(return_annotation="numero")
print(nova)Alterar a assinatura exibida não muda automaticamente o comportamento do callable. Um framework deve manter contrato e implementação sincronizados.
Herança e ordem de resolução
getmro() devolve a ordem de resolução de métodos.
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(inspect.getmro(D))Essa ordem explica qual implementação será encontrada em herança múltipla. getclasstree() organiza um conjunto de classes em uma estrutura hierárquica.
Closures e variáveis externas
getclosurevars() informa referências não locais, globais, built-ins e nomes não resolvidos.
fator = 10
def criar():
adicional = 2
def calcular(valor):
return valor * fator + adicional
return calcular
print(inspect.getclosurevars(criar()))É útil em depuração e análise de dependências, mas expõe referências vivas. Não registre valores secretos.
Estado de generators e coroutines
getgeneratorstate() retorna estados como criado, executando, suspenso ou fechado.
def contador():
yield 1
yield 2
gen = contador()
print(inspect.getgeneratorstate(gen))
next(gen)
print(inspect.getgeneratorstate(gen))Há funções equivalentes para coroutines e generators assíncronos. Elas são úteis em testes de schedulers e ferramentas de diagnóstico.
Variáveis locais de generators
getgeneratorlocals(), getcoroutinelocals() e getasyncgenlocals() recuperam o estado local enquanto há um frame associado.
Esse recurso depende de detalhes da implementação e pode retornar um dicionário vazio em outros interpretadores. Não construa lógica de negócio dependente dele.
Frames e pilha de execução
currentframe(), stack(), trace(), getouterframes() e getinnerframes() ajudam depuradores e relatórios de erro.
frame = inspect.currentframe()
try:
if frame is not None:
info = inspect.getframeinfo(frame)
print(info.filename, info.lineno, info.function)
finally:
del framecurrentframe() pode retornar None em implementações sem suporte a frames Python.
Frames podem causar vazamentos de memória
Frames referenciam variáveis locais e frames externos. Manter uma referência pode criar ciclos e prolongar a vida de muitos objetos.
Sempre remova a referência em finally, como no exemplo anterior. Se precisar guardar um frame para diagnóstico, chame frame.clear() quando terminar. Esse cuidado é essencial em servidores de longa duração.
Introspecção não é uma API pública automática
Descobrir um atributo não significa que ele seja estável ou permitido para uso. Nomes com underscore, code objects e atributos de frames podem ser detalhes internos.
Plugins devem declarar contratos explícitos, versões e interfaces. Use introspecção para adaptação e diagnóstico, não para substituir totalmente uma especificação.
Compatibilidade entre implementações
Alguns built-ins de CPython não fornecem metadados completos de assinatura. Descriptors e code objects também podem variar em outras implementações.
Capture TypeError, ValueError e OSError, forneça fallbacks e teste nos interpretadores suportados.
Exemplo: registrador de handlers
def registrar_handler(func):
if not inspect.isfunction(func) and not inspect.ismethod(func):
raise TypeError("Handler deve ser função ou método")
sig = inspect.signature(func)
parametros = list(sig.parameters.values())
if not parametros:
raise TypeError("Handler precisa receber um evento")
return {
"callable": func,
"assinatura": sig,
"documentacao": inspect.getdoc(func) or "",
}Antes de executar, use bind() com o evento e dependências disponíveis. Isso produz erros claros durante a configuração.
Erros frequentes
- Usar
getmembers()em objetos não confiáveis sem considerar properties. - Presumir que todo callable possui assinatura ou fonte recuperável.
- Avaliar annotations como strings sem analisar o risco.
- Criar decorators sem
functools.wraps(). - Confundir função da classe com método vinculado.
- Manter frames e criar ciclos de referência.
- Depender de detalhes exclusivos do CPython.
- Usar introspecção como substituto de um contrato de plugin.
Boas práticas
- Use predicados
is*para deixar a intenção explícita. - Prefira
signature()a APIs antigas de argumentos. - Use
getmembers_static()para inspeção passiva. - Trate ausência de fonte e assinatura como caso normal.
- Preserve
__wrapped__comwraps(). - Evite avaliar annotations não confiáveis.
- Libere referências a frames em
finally. - Teste em todas as implementações de Python suportadas.
Conclusão
O módulo inspect no Python oferece ferramentas para examinar objetos vivos, classes, funções, signatures, código-fonte, closures, generators e a pilha do interpretador. Ele permite construir documentação automática, registradores, depuradores e validadores que se adaptam aos objetos em execução.
Introspecção exige cuidado porque a leitura de atributos pode executar descriptors, annotations podem envolver avaliação e frames podem manter grandes grafos de objetos vivos. Ao preferir APIs estáticas quando necessário, tratar metadados ausentes, preservar wrappers e liberar frames, você aproveita a flexibilidade dinâmica do Python sem transformar ferramentas de diagnóstico em efeitos colaterais ou vazamentos de memória.







