O módulo dis desmonta code objects do Python e mostra as instruções de bytecode executadas pelo interpretador. Ele ajuda a estudar como expressões, funções, loops, comprehensions, exceções e chamadas são compiladas. Também é útil para depuração de ferramentas, ensino, análise de desempenho e investigação de diferenças entre versões.
Bytecode é um detalhe de implementação. Instruções, argumentos, offsets, caches e otimizações podem mudar entre releases, inclusive versões menores. Não escreva lógica de negócio, formato persistente ou ferramenta de segurança que dependa rigidamente de uma sequência específica sem limitar e testar a versão do Python.
Desmonte uma função
dis.dis() aceita funções, métodos, classes, módulos, strings de código e code objects.
import dis
def somar(a, b):
return a + b
dis.dis(somar)
A saída mostra offsets, instruções, argumentos e representações úteis dos operandos.
Compile uma expressão
Também é possível estudar código compilado dinamicamente.
codigo = compile("resultado = x * 2", "exemplo.py", "exec")
dis.dis(codigo)
compile() não executa o código, mas o conteúdo ainda deve ser tratado como não confiável se veio de outra pessoa.
Code objects
Funções possuem um atributo __code__ com bytecode, constantes, nomes, variáveis e metadados.
code = somar.__code__
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)
Esses campos são voltados a introspecção e podem evoluir.
Instruções com get_instructions
dis.get_instructions() retorna objetos Instruction, melhores para análise programática que o texto de dis().
for instrucao in dis.get_instructions(somar):
print(
instrucao.offset,
instrucao.opname,
instrucao.arg,
instrucao.argval,
)
Prefira campos nomeados em vez de fazer parsing da saída formatada.
Instruction
Um objeto de instrução pode conter nome do opcode, argumento numérico, valor resolvido, representação, offset, posição de origem e marcadores de jump target.
Nem todo campo tem significado para todo opcode. Trate valores ausentes.
Bytecode
A classe dis.Bytecode oferece uma interface iterável e métodos de formatação.
bytecode = dis.Bytecode(somar)
for instrucao in bytecode:
print(instrucao.opname)
print(bytecode.dis())
É útil quando a ferramenta precisa guardar o objeto analisado e opções de visualização.
Linhas e posições
As instruções podem ser associadas a linhas e intervalos do código-fonte. Python moderno mantém metadados de posição mais detalhados.
Use esses dados para diagnósticos, mas aceite posições ausentes em código gerado ou transformado.
show_offsets
Opções atuais de desmontagem podem mostrar offsets explicitamente. Eles ajudam a interpretar jumps e exception tables.
Offsets não devem ser comparados entre versões ou builds como se fossem IDs estáveis.
Instruções de carga
Operações como carregar constantes, nomes, globals, locals e atributos aparecem com opcodes específicos.
A escolha exata depende de escopo e otimizações. Compare:
x = 10
def usar_global():
return x
def usar_local(x):
return x
O bytecode ajuda a visualizar a diferença, mas não deve substituir o entendimento de escopo da linguagem.
Operações binárias
Expressões aritméticas são compiladas em instruções que carregam operandos e aplicam uma operação.
def calcular(a, b):
return (a + b) * 2
Em versões modernas, várias operações podem compartilhar um opcode com argumento que indica a operação concreta.
Constantes dobradas
O compilador pode calcular expressões constantes antecipadamente.
def exemplo():
return 2 + 3
A desmontagem pode mostrar apenas a constante final. Não conclua que toda expressão aparentemente constante será sempre dobrada.
Loops
Um for normalmente envolve obtenção do iterator, leitura do próximo item e jumps.
def totalizar(itens):
total = 0
for item in itens:
total += item
return total
Estudar o fluxo ajuda no ensino de iterators e controle, mas otimizações podem alterar a forma.
Condições e jumps
if, while, expressões booleanas e short-circuit usam instruções de jump.
O atributo is_jump_target identifica instruções que recebem desvios.
for instrucao in dis.get_instructions(funcao):
if instrucao.is_jump_target:
print("alvo", instrucao.offset)
Jumps relativos
A representação de jumps e offsets mudou em versões recentes. Use argval e APIs do módulo, não cálculos manuais baseados em bytes históricos.
Ferramentas devem declarar as versões suportadas.
Chamadas de função
Chamadas envolvem carregamento do callable, argumentos e instruções específicas de preparação e execução.
O bytecode pode mudar conforme chamadas posicionais, keyword arguments, métodos e otimizações adaptativas.
Comprehensions
List, set e dict comprehensions podem gerar code objects internos.
def pares(valores):
return [x * 2 for x in valores if x % 2 == 0]
dis.dis() também desmonta code objects aninhados em muitos casos, permitindo ver a função implícita.
Generators
Funções com yield possuem instruções e flags específicas para suspensão e retomada.
Não use a desmontagem para alterar diretamente o estado de um generator. Use APIs públicas.
async e await
Coroutines e generators assíncronos geram bytecode próprio para espera, envio e retomada.
A forma varia bastante entre versões; use para estudo da versão em execução.
Exceções
Tratamento de exceções utiliza metadados e instruções que mudaram significativamente na evolução do CPython.
Não tente encontrar blocos try apenas procurando opcodes históricos. Use as APIs e metadados atuais.
Exception tables
Code objects modernos podem manter tabelas compactas de tratamento em vez de instruções explícitas no fluxo principal.
A saída do dis apresenta informações úteis, mas o formato interno não é contrato de estabilidade.
Adaptive interpreter
CPython pode especializar instruções durante a execução com base nos tipos observados. Isso melhora desempenho sem alterar o código-fonte.
A desmontagem padrão pode mostrar bytecode original ou adaptado conforme opções e estado da função.
adaptive
As APIs atuais oferecem opções para exibir instruções especializadas quando disponíveis.
dis.dis(funcao, adaptive=True)
Execute a função várias vezes antes se quiser observar especialização, mas não presuma que ela ocorrerá de maneira idêntica em todas as máquinas.
show_caches
Inline caches podem aparecer com show_caches=True.
dis.dis(funcao, show_caches=True)
Esses caches são internals de desempenho. Não os modifique e não baseie decisões funcionais neles.
Bytecode muda em runtime
A especialização pode alterar a forma observada depois de warm-up. Uma ferramenta que compara desmontagens precisa controlar se mostra código adaptativo.
Registre versão do Python, opções e quantidade de execuções.
stack_effect
dis.stack_effect(opcode, oparg) calcula o efeito de uma instrução na pilha de avaliação.
import dis
import opcode
efeito = dis.stack_effect(opcode.opmap["LOAD_CONST"], 0)
print(efeito)
Para jumps condicionais, a API pode aceitar informação sobre o ramo considerado.
Análise da pilha
Somar efeitos linearmente não basta em um grafo com jumps. Uma análise correta propaga alturas por blocos de controle e verifica convergência.
Use ferramentas especializadas quando precisar validar bytecode.
opcode
O módulo opcode fornece mapas e categorias de opcodes.
import opcode
print(opcode.opmap.get("RETURN_VALUE"))
Os números mudam entre versões. Use nomes e mapas da versão atual.
Comparação de implementações
dis descreve bytecode do runtime compatível com sua API, principalmente CPython. Outras implementações podem ter representação diferente.
Não generalize conclusões de CPython para toda a linguagem Python.
Otimização
Bytecode pode revelar alocação de comprehensions, loads repetidos e chamadas dentro de loops. Ainda assim, não otimize apenas pela aparência.
Meça com timeit, cProfile e workloads reais. Uma instrução a menos pode não produzir ganho relevante.
Microbenchmarks
Comparar duas formas de código exige warm-up, repetição, isolamento e controle de ruído.
O interpretador adaptativo torna ainda mais importante medir estado estável.
Segurança
Encontrar um opcode não prova que o código é seguro ou perigoso. Aliases, imports, objetos e comportamento dinâmico escapam de uma allowlist simples.
Não crie sandbox validando bytecode e depois chamando exec(). Use isolamento de processo e uma linguagem restrita.
Code objects não confiáveis
Não carregue code objects de marshal ou pickle não confiável para desmontar no mesmo processo crítico.
Mesmo que você não execute explicitamente, parsers e estruturas internas não devem ser expostos sem limites a payload hostil.
Ferramentas de diff
Para comparar versões, normalize offsets, caches e campos instáveis. Compare opnames e argumentos sem exigir identidade byte a byte.
Separe mudanças semânticas de diferenças de implementação.
CLI
O módulo pode desmontar arquivos pela linha de comando.
python -m dis modulo.py
É útil para aprendizado e inspeção rápida.
Integração com AST
A AST mostra estrutura de alto nível; dis mostra instruções após compilação. Comparar as duas camadas ajuda a entender otimizações.
Veja ast no Python.
Integração com tokenize
tokenize preserva comentários e grafia, que não aparecem no bytecode.
O artigo sobre tokenize no Python explica a camada lexical.
Testes de ferramentas
Execute a suíte em cada versão suportada. Inclua funções simples, closures, generators, async, comprehensions, exceções e pattern matching.
Use fixtures por versão quando a saída esperada depende de opcodes.
Erros comuns
Os erros mais frequentes são assumir opcodes estáveis, fazer parsing do texto do dis, comparar offsets entre versões, ignorar caches adaptativos, otimizar sem medir, tratar bytecode como sandbox, generalizar CPython para outras implementações e carregar code objects não confiáveis.
Conclusão
dis permite observar como o Python compila e executa funções no nível do bytecode. Use get_instructions() para análise programática, controle opções de caches e especialização e sempre registre a versão do runtime.
Trate o bytecode como detalhe mutável e use profiling antes de otimizar. Consulte a documentação oficial de dis e a documentação de opcode.







