O código Python é compilado para instruções intermediárias executadas pela máquina virtual do interpretador. Essas instruções formam o bytecode do CPython. O módulo dis no Python desmonta funções, métodos, classes, coroutines e trechos de código, permitindo observar cargas de variáveis, chamadas, saltos, operadores, caches adaptativos e a relação entre linhas-fonte e operações internas.
Este guia explica dis.dis(), Bytecode, get_instructions(), code_info(), análise de tracebacks e bytecode especializado. Ele complementa nossos artigos sobre scripts Python lentos, cProfile, timeit, pdb e inspect.
O que é bytecode
Ao carregar uma função, o CPython compila sua sintaxe para um objeto de código. Esse objeto contém constantes, nomes, variáveis locais, flags, posições de origem e uma sequência de instruções.
def dobro(numero):
return numero * 2
print(dobro.__code__)
print(dobro.__code__.co_consts)
print(dobro.__code__.co_varnames)O bytecode não é código de máquina do processador. Ele é uma representação específica do interpretador, consumida pelo loop de avaliação do CPython.
Primeiro exemplo com dis()
dis.dis() imprime a desmontagem de uma função.
import dis
def dobro(numero):
return numero * 2
dis.dis(dobro)A saída pode conter operações como RESUME, LOAD_FAST, LOAD_CONST, BINARY_OP e RETURN_VALUE. Os nomes e detalhes variam entre versões.
Bytecode é detalhe de implementação
A documentação oficial de dis enfatiza que o bytecode é um detalhe do CPython. Instruções podem ser adicionadas, removidas ou alteradas entre versões e outros interpretadores podem utilizar modelos diferentes.
Use dis para aprendizado, diagnóstico, testes de compilador e ferramentas vinculadas a uma versão. Não use opcodes como contrato portátil de negócio.
Analisar strings e objetos de código
dis() também aceita uma string de código, um módulo, uma classe, um método, uma coroutine ou um objeto retornado por compile().
dis.dis("resultado = soma(a, b)")
codigo = compile(
"total = preco * quantidade",
"exemplo.py",
"exec",
)
dis.dis(codigo)Ao receber uma classe, a função percorre métodos e objetos de código aninhados até a profundidade configurada.
Controlar a profundidade
Funções aninhadas, comprehensions e generator expressions possuem objetos de código próprios. O argumento depth controla a recursão.
def externa():
def interna(valor):
return valor + 1
return [interna(x) for x in range(3)]
dis.dis(externa, depth=0)
dis.dis(externa, depth=2)Use uma profundidade pequena ao analisar módulos ou classes grandes para manter a saída legível.
Informações do objeto de código
code_info() retorna um resumo com quantidade de argumentos, variáveis, constantes, nomes, flags e tamanho da pilha.
print(dis.code_info(externa))show_code() imprime o mesmo tipo de informação diretamente.
dis.show_code(externa)Esses dados são úteis antes de examinar as instruções individuais.
A classe Bytecode
dis.Bytecode reúne a função analisada e permite obter informações formatadas ou iterar por instruções.
bytecode = dis.Bytecode(dobro)
print(bytecode.info())
print(bytecode.dis())
for instrucao in bytecode:
print(instrucao.opname, instrucao.argrepr)A classe aceita funções, generators, coroutines, strings e objetos de código.
get_instructions() para dados estruturados
Quando uma ferramenta precisa processar operações em vez de imprimir texto, use get_instructions().
for instrucao in dis.get_instructions(dobro):
print({
"operacao": instrucao.opname,
"offset": instrucao.offset,
"argumento": instrucao.argval,
"linha": instrucao.line_number,
})Cada item é um objeto Instruction com opcode numérico, nome, argumento resolvido, posição, destino de salto e informações de cache.
Entender LOAD e STORE
Operações que começam com LOAD colocam referências ou valores na pilha de avaliação. STORE retira um valor e o associa a uma variável, atributo ou coleção.
def exemplo(valor):
resultado = valor + 10
return resultado
dis.dis(exemplo)LOAD_FAST acessa variáveis locais. LOAD_CONST carrega constantes. STORE_FAST grava em um slot local. Globais, atributos e closures possuem instruções próprias.
A pilha de avaliação
Muitas instruções consomem operandos do topo da pilha e colocam um resultado. Em uma soma, por exemplo, o interpretador carrega os dois valores e executa uma operação binária.
def somar(a, b):
return a + b
dis.dis(somar)dis.stack_effect() calcula a mudança líquida causada por um opcode.
opcode = dis.opmap["LOAD_CONST"]
print(dis.stack_effect(opcode, 0))Essa função interessa a ferramentas que validam ou simulam fluxos de bytecode.
Saltos e estruturas de controle
if, loops, short circuit e tratamento de exceções são representados por comparações e saltos.
def classificar(numero):
if numero >= 0:
return "positivo"
return "negativo"
dis.dis(classificar, show_offsets=True)Versões recentes exibem labels lógicos para destinos. Não calcule manualmente offsets sem considerar caches e mudanças específicas da versão.
Posições de código no Python 3.14
O Python 3.14 adicionou show_positions=True e a opção de linha de comando -P. A saída pode incluir linha inicial, linha final e colunas cobertas por uma instrução.
dis.dis(
classificar,
show_offsets=True,
show_positions=True,
)As posições detalhadas ajudam depuradores, cobertura, análise estática e mensagens que apontam uma expressão específica.
Executar dis pela linha de comando
O módulo pode desmontar um arquivo ou código recebido pela entrada padrão.
python -m dis programa.pyOpções atuais incluem:
-Cpara caches;-Opara offsets;-Ppara posições;-Spara bytecode especializado.
Essas opções dependem da versão instalada.
Caches inline
Desde o Python 3.11, determinadas instruções possuem espaço para caches usados na especialização do interpretador. show_caches=True revela esses dados na desmontagem.
dis.dis(dobro, show_caches=True)Os caches pertencem logicamente à instrução anterior. Não interprete seus bytes como opcodes independentes nem modifique bytecode adaptativo bruto.
Bytecode adaptativo e especializado
O CPython pode adaptar operações após observar tipos e padrões durante a execução. Use adaptive=True ou, na CLI do Python 3.14, -S.
for _ in range(20_000):
dobro(10)
dis.dis(dobro, adaptive=True, show_caches=True)A especialização é dinâmica. O resultado pode variar conforme aquecimento, build, arquitetura e estado do processo.
Não confunda desmontagem com benchmark
Uma sequência menor de instruções não garante melhor desempenho. Uma única operação pode chamar código complexo, criar objetos ou executar I/O. Confirme qualquer hipótese com timeit, cProfile e métricas reais.
def com_sum(valores):
return sum(valores)
def com_loop(valores):
total = 0
for valor in valores:
total += valor
return totaldis explica parte da diferença estrutural, mas o benchmark mede o resultado.
Analisar uma falha com distb()
dis.distb() desmonta a função no topo de um traceback e marca a instrução que provocou a exceção.
try:
valor = (1, 2)[5]
except IndexError as erro:
dis.distb(erro.__traceback__)Outra opção é construir Bytecode.from_traceback().
bytecode = dis.Bytecode.from_traceback(erro.__traceback__)
print(bytecode.dis())Esse recurso combina bem com o artigo sobre formatação de tracebacks.
Closures e variáveis livres
Funções internas podem acessar células da função externa. A desmontagem mostra operações como MAKE_CELL, LOAD_DEREF e COPY_FREE_VARS.
def multiplicador(fator):
def aplicar(valor):
return valor * fator
return aplicar
dis.dis(multiplicador)Essas instruções ajudam a visualizar como closures preservam referências.
Generators e coroutines
Generators, yield from, await e async generators possuem operações como RETURN_GENERATOR, YIELD_VALUE, SEND e GET_AWAITABLE.
async def buscar(cliente):
return await cliente.obter()
dis.dis(buscar)A saída muda significativamente entre versões, pois o interpretador evolui a execução assíncrona.
Comprehensions
Uma list comprehension costuma ser compilada como um objeto de código aninhado ou otimizada de forma específica pela versão.
def pares(limite):
return [n * 2 for n in range(limite)]
dis.dis(pares, depth=2)Observe operações de iteração, anexação e chamadas. Não transforme automaticamente a compreensão em loop apenas pela aparência do bytecode.
Opcode collections
O módulo expõe coleções para introspecção:
print(dis.opmap["RETURN_VALUE"])
print(dis.opname[dis.opmap["RETURN_VALUE"]])
print(dis.hasconst)
print(dis.hasjump)opmap associa nomes a códigos; opname faz o caminho inverso. As coleções hasconst, hasname, hasfree e hasjump classificam opcodes.
Testes que verificam bytecode
Bibliotecas muito próximas do interpretador podem testar a presença de uma operação, mas esses testes devem ser condicionados à versão.
import sys
operacoes = {
item.opname
for item in dis.get_instructions(dobro)
}
if sys.implementation.name == "cpython":
assert "RETURN_VALUE" in operacoesEvite snapshots textuais rígidos, pois labels, offsets, caches e formatação mudam.
Não execute código não confiável
dis.dis() sobre uma string usa compilação, não execução, mas analisar módulos pode exigir importá-los antes. Imports executam código de nível superior. Faça análise de arquivos não confiáveis em processo isolado, sem segredos e com permissões limitadas.
Erros frequentes
- Tratar opcodes como API estável.
- Comparar bytecode de versões diferentes sem contexto.
- Inferir desempenho apenas pela quantidade de instruções.
- Ignorar caches e especialização.
- Construir ferramentas dependentes de offsets antigos.
- Importar código não confiável para analisá-lo.
- Alterar
co_codediretamente. - Usar snapshots textuais frágeis em testes.
Boas práticas
- Registre versão e implementação do Python.
- Use
get_instructions()para análise estruturada. - Use posições e labels em vez de cálculos frágeis.
- Confirme hipóteses de velocidade com benchmarks.
- Limite a profundidade em objetos grandes.
- Analise bytecode adaptativo somente após aquecimento controlado.
- Isole código desconhecido.
- Trate mudanças entre releases como comportamento esperado.
Conclusão
O módulo dis no Python revela como o CPython transforma construções da linguagem em operações da máquina virtual. Ele ajuda a compreender variáveis locais, chamadas, loops, closures, generators, exceções, caches e especialização do interpretador.
Essa visão é poderosa, mas específica da implementação e da versão. Use a desmontagem como ferramenta de aprendizado e diagnóstico, não como contrato portátil nem substituto de medições. Com Bytecode, get_instructions(), posições de origem e comparação controlada, você pode investigar o funcionamento interno sem depender de suposições frágeis sobre opcodes.







