dis no Python: entenda o bytecode

Publicado em: 03/08/2026
Tempo de leitura: 7 minutos
Monitor com código binário representando análise de bytecode com dis no Python

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.py

Opções atuais incluem:

  • -C para caches;
  • -O para offsets;
  • -P para posições;
  • -S para 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 total

dis 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 operacoes

Evite 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_code diretamente.
  • 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Programador analisando código para identificar tipos MIME de arquivos no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes.guess_file_type: detecte tipos MIME

    Aprenda mimetypes.guess_file_type no Python para detectar tipos MIME em caminhos, URLs, uploads e respostas HTTP com fallbacks seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Componentes de servidor representando interpretadores Python executando em paralelo
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: paralelismo real no Python

    Aprenda InterpreterPoolExecutor no Python para executar tarefas CPU-bound em interpretadores isolados com paralelismo real.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocessador representando CPUs disponíveis para um processo Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: conte CPUs disponíveis

    Aprenda os.process_cpu_count no Python para dimensionar workers conforme as CPUs realmente disponíveis ao processo.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026
    Laptop com código digital representando dados BLOB no SQLite
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    sqlite3.Blob: leia BLOBs sem carregar tudo na memória

    Aprenda sqlite3.Blob no Python para ler e gravar BLOBs em partes, reduzir memória e trabalhar com dados binários no SQLite.

    Ler mais

    Tempo de leitura: 5 minutos
    12/09/2026
    Análise estatística para random.binomialvariate no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    random.binomialvariate: simule distribuições binomiais

    Aprenda random.binomialvariate no Python para simular sucessos, validar probabilidades e analisar cenários binomiais com clareza.

    Ler mais

    Tempo de leitura: 6 minutos
    11/09/2026
    Código Python analisado com inspect.signature.bind
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    inspect.signature.bind: valide argumentos de funções

    Aprenda inspect.signature.bind no Python para validar argumentos, aplicar padrões e criar decorators e APIs dinâmicas com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    11/09/2026