Inspetor de bytecode .pyc do Python
Um .pyc é a forma compilada que o CPython escreve ao lado do fonte, dentro de __pycache__, e tem exatamente duas partes. Os primeiros dezesseis bytes são o cabeçalho: um número mágico de quatro bytes que nomeia a versão exata do CPython que escreveu o arquivo (não a que você tem instalada, e sim a que o compilou), seguido de uma palavra de flags. O bit 0 decide como o cache é invalidado. Em zero, os oito bytes seguintes guardam a data de modificação do fonte e o tamanho dele, e o sistema de importação descarta o cache assim que qualquer um dos dois deixa de bater com o .py em disco. Em um, como permite a PEP 552, esses oito bytes guardam um hash do fonte, e o bit 1 diz se o interpretador o reconfere a cada importação ou se confia cegamente, que é o que uma compilação reproduzível quer. Atrás do cabeçalho há um único objeto de código serializado pelo marshal, e cada função, lambda, compreensão e corpo de classe do módulo está aninhado dentro das constantes dele como outro objeto de código: por isso a árvore aqui desce vários níveis. De cada um você vê os nomes que ele alcança, as variáveis locais, de célula e livres, a contagem de argumentos, o tamanho da pilha, os bits de flag decodificados, cada constante escrita como repr() a escreveria e um desmontador com EXTENDED_ARG dobrado na instrução a que pertence e todo salto resolvido para um deslocamento absoluto. A numeração dos opcodes depende da versão e muda quase a cada lançamento, então a tabela é escolhida pelo número mágico e nomeada na tela: da 3.7 à 3.13, e um arquivo de qualquer outra versão tem o cabeçalho lido e o corpo deixado em paz, em vez de decodificado com a tabela errada. O que ele não faz é devolver o seu fonte. Isto não é um descompilador: nomes, docstrings e constantes sobrevivem inteiros, mas o fluxo que você vê é bytecode, não o laço que você escreveu, e comentários e formatação foram descartados pelo compilador muito antes de este arquivo existir. A tabela de números de linha e, a partir da 3.11, a de exceções são puladas sem decodificação.
Como usar
- Solte um .pyc de uma pasta __pycache__: o nome costuma parecer modulo.cpython-311.pyc.
- Olhe primeiro "Escrito por": é a versão do CPython que compilou o arquivo, e um interpretador com outro número mágico ignora o cache e recompila a partir do fonte.
- Confira o modo de invalidação. Um arquivo por data carrega a data e o tamanho do fonte; um por hash carrega um hash do fonte, e a variante sem verificação nunca mais é comparada com ele.
- Escolha um objeto de código na árvore. No topo está o módulo, e cada função, corpo de classe, lambda e (antes da 3.12) compreensão fica pendurado sob aquilo que a define.
- Leia o desmontador do objeto selecionado: a coluna Resolvido troca índices por nomes, locais e constantes, e >> marca uma instrução onde algum salto cai.
Perguntas frequentes
- Editei o fonte, mas o Python continua rodando o código antigo. Por quê?
- Um .pyc invalidado por data guarda a modificação do fonte com precisão de um segundo e o tamanho em bytes, e o CPython reaproveita o cache enquanto os dois continuarem batendo. Duas situações derrotam isso. Se uma edição cair dentro do mesmo segundo da data registrada e deixar o arquivo com o mesmo comprimento — uma troca de um caractere num arquivo gerado, por exemplo — nada parece diferente e o cache velho é usado. E restaurar arquivos com uma ferramenta que preserva datas, ou trocar para um branch que as recua, pode deixar um .pyc mais novo que um fonte com o qual ele já não corresponde. Apagar a pasta __pycache__ resolve os dois casos; rodar com -B ou com PYTHONDONTWRITEBYTECODE evita que os arquivos sejam escritos.
- Qual a diferença entre um .pyc por hash verificado e um sem verificação?
- A PEP 552 trocou a data por um hash de oito bytes do fonte, para que a compilação deixe de depender das datas dos arquivos: o mesmo fonte produz um .pyc idêntico byte a byte em qualquer máquina, que é o que compilações reproduzíveis exigem. O bit 1 da palavra de flags escolhe o comportamento: verificado quer dizer que o interpretador calcula o hash do fonte a cada importação e recompila se diferir; sem verificação quer dizer que ele não olha o fonte de jeito nenhum. O segundo caso serve quando algo fora do Python garante que está atualizado, como uma imagem de contêiner construída de uma vez, e é uma armadilha em qualquer outro lugar, porque editar o fonte não surte efeito algum até o arquivo ser regerado à mão.
- Dá para recuperar o fonte original a partir de um .pyc?
- Com esta ferramenta não, e com nenhuma por completo. O compilador guarda tudo o que precisa para executar: cada nome, cada constante, as docstrings, os nomes dos argumentos e a primeira linha de cada função, de modo que um .pyc vaza muito mais do que se imagina e nunca deve ser tratado como ofuscação. O que ele não guarda é o texto: comentários, linhas em branco, formatação e o formato exato das expressões somem. Descompiladores como uncompyle6 ou decompyle3 reconstroem um fonte plausível a partir do fluxo de instruções, mas ficam anos atrás da linguagem e param por volta da 3.9, porque os compiladores novos reorganizam o controle de fluxo de um jeito que não volta mais um para um.
- Por que o mesmo fonte gera bytecode completamente diferente em duas versões do Python?
- Porque o conjunto de instruções é um detalhe de implementação que o CPython reescreve à vontade. A 3.11 introduziu caches em linha — bytes reais no fluxo, pertencentes à instrução anterior — e moveu o tratamento do frame para opcodes novos como RESUME e MAKE_CELL. A 3.12 embutiu as compreensões de lista e de conjunto, que deixaram de ser objetos de código separados. A 3.13 renumerou quase tudo de novo. É exatamente para isso que existe o número mágico: um .pyc só vale para o lançamento que o escreveu, e qualquer outro interpretador o trata como inexistente.
- Por que os deslocamentos pulam mais de dois, e o que é EXTENDED_ARG?
- Cada instrução ocupa dois bytes, um de opcode e um de argumento, então um argumento maior que 255 é montado por uma ou mais instruções EXTENDED_ARG à frente dele, cada uma contribuindo com mais oito bits. Aqui elas são dobradas na instrução seguinte, exatamente como o dis faz, e por isso você verá argumentos na casa dos milhares numa única linha. A partir da 3.11 o fluxo também contém espaços de cache em linha que pertencem à instrução anterior e guardam o estado de especialização em tempo de execução; eles são pulados em vez de listados, então os deslocamentos avançam mais de dois. Num .pyc em disco esses espaços são sempre zero: a especialização acontece na memória, e os opcodes acelerados nunca são escritos em arquivo.
- O que são variáveis de célula e livres, e por que um nome aparece em duas listas?
- Um fechamento precisa que a variável viva mais que o frame que a criou, então o compilador a coloca numa célula: a função que a define a lista entre suas variáveis de célula, e cada função aninhada que a lê lista o mesmo nome entre suas variáveis livres. A partir da 3.11 as duas ficam num único array com um byte de tipo por entrada, e é por isso que um argumento capturado por uma função aninhada aparece ao mesmo tempo como local e como variável de célula: ele é mesmo as duas coisas, e a função começa com uma instrução MAKE_CELL que move o argumento para a célula dele.
Ferramentas relacionadas
Inspetor de pickle do Python
Desmonte um .pkl no navegador: cada opcode, o memo, o valor que ele remonta e se carregá-lo executaria código.
Inspetor de arquivos .class do Java
Analise um .class compilado no navegador: versão do arquivo e do Java, sinalizadores de acesso, pool de constantes, campos, métodos e dependências.
Inspetor de .npy e .npz do NumPy
Leia o cabeçalho de um .npy ou .npz no navegador: dtype, shape, ordem de bytes, campos e uma prévia de valores, sem NumPy e sem upload.
Inspetor de binários ELF
Abra um executável Linux, um .so ou um .o no navegador: arquitetura, bibliotecas necessárias, Build ID e se tem PIE, NX e RELRO.
Detector de Tipo de Arquivo (Magic Bytes)
Solte um arquivo pra identificar seu formato real a partir dos magic bytes — e pegue arquivos cuja extensão ou MIME declarado mente.
Inspetor de bancos de dados SQLite
Abra um arquivo .sqlite ou .db no navegador e leia sua estrutura: tamanho de página, codificação, modo de journal e as linhas reais de cada tabela.