inspect no Python: introspecção de objetos

Publicado em: 02/08/2026
Tempo de leitura: 8 minutos
Análise de software representando introspecção de objetos com inspect no Python

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 getter

O 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_ONLY para argumentos antes de /;
  • POSITIONAL_OR_KEYWORD para o caso comum;
  • VAR_POSITIONAL para *args;
  • KEYWORD_ONLY para parâmetros depois de *;
  • VAR_KEYWORD para **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 frame

currentframe() 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__ com wraps().
  • 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Módulo de memória representando referências fracas e caches no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    weakref no Python: referências fracas

    Aprenda weakref no Python para criar referências fracas, caches automáticos, observadores e finalizadores sem reter objetos na memória.

    Ler mais

    Tempo de leitura: 9 minutos
    28/07/2026
    Ícone de arquivo ZIP para artigo sobre zipfile no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    zipfile no Python: arquivos ZIP seguros

    Aprenda a criar, ler, validar e extrair arquivos ZIP com zipfile no Python de forma previsível e segura.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026
    Grafo de dependências e fluxo de tarefas em Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    graphlib no Python: ordenação topológica

    Aprenda graphlib no Python para ordenar dependências, detectar ciclos e executar tarefas independentes em paralelo.

    Ler mais

    Tempo de leitura: 6 minutos
    27/07/2026
    Código Python para contexto seguro em aplicações assíncronas
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextvars no Python: contexto seguro

    Aprenda a usar contextvars no Python para isolar contexto em asyncio, logs, threads e testes sem depender de variáveis globais.

    Ler mais

    Tempo de leitura: 7 minutos
    26/07/2026
    Código Python usando cached_property para armazenar cálculos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    cached_property no Python: cache em objetos

    Aprenda cached_property no Python para armazenar cálculos caros, invalidar valores e evitar caches desatualizados em objetos.

    Ler mais

    Tempo de leitura: 7 minutos
    25/07/2026
    Código Python com funções especializadas por tipo
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    singledispatch no Python: polimorfismo simples

    Aprenda singledispatch no Python para criar funções por tipo, reduzir isinstance e organizar polimorfismo com exemplos práticos.

    Ler mais

    Tempo de leitura: 6 minutos
    25/07/2026