Quando um módulo Python é importado, o interpretador pode armazenar seu bytecode em um arquivo .pyc dentro de __pycache__. O módulo py_compile no Python permite gerar esse cache antecipadamente, validar arquivos durante builds e controlar como o interpretador decide se o bytecode ainda corresponde ao código-fonte.
Neste guia, você aprenderá a usar py_compile.compile(), tratar PyCompileError, escolher otimização e configurar invalidação por timestamp ou hash. O conteúdo complementa nossos artigos sobre bytecode com dis, compileall, tracebacks, tokenize e compilação interativa.
O que é um arquivo pyc
Um arquivo .pyc contém bytecode serializado e um cabeçalho com informações usadas para validar o cache. Ele evita recompilar o mesmo código-fonte em determinadas inicializações, mas não transforma Python em um binário independente.
O bytecode continua específico da implementação e da versão. Um arquivo produzido por uma versão do CPython normalmente não deve ser reutilizado por outra tag de cache.
Compilar um arquivo
A função principal recebe o caminho do arquivo-fonte.
import py_compile
caminho = py_compile.compile("meu_modulo.py")
print(caminho)Por padrão, o resultado segue as convenções PEP 3147 e PEP 488, normalmente dentro de __pycache__ com a tag do interpretador.
Validar sintaxe durante o build
Compilar previamente é uma maneira simples de detectar SyntaxError antes da implantação.
from pathlib import Path
import py_compile
for arquivo in Path("src").rglob("*.py"):
py_compile.compile(
str(arquivo),
doraise=True,
)Para árvores inteiras, o módulo compileall oferece uma interface mais conveniente. py_compile é adequado quando a aplicação já possui a lista exata de arquivos.
doraise e PyCompileError
Por padrão, um erro é escrito em stderr e a função retorna None. Com doraise=True, a falha gera PyCompileError.
try:
py_compile.compile(
"quebrado.py",
doraise=True,
)
except py_compile.PyCompileError as erro:
print("Compilação falhou:", erro)Aplicações e pipelines devem preferir a exceção, pois ela permite controlar o status e produzir relatórios estruturados.
Definir o destino com cfile
cfile escolhe explicitamente o arquivo de saída.
py_compile.compile(
"modulo.py",
cfile="build/modulo.pyc",
doraise=True,
)Crie o diretório de destino antes. Um caminho personalizado pode ser útil em empacotamento, mas as convenções padrão facilitam importação e coexistência entre versões.
Proteção contra symlinks
Se o destino calculado for um link simbólico ou um arquivo não regular, a função lança FileExistsError.
A documentação oficial de py_compile explica que a gravação segue a semântica de importlib, com escrita temporária e renomeação para reduzir problemas de concorrência. A proteção evita substituir silenciosamente objetos especiais.
Nome de arquivo nos tracebacks
dfile define o nome que aparecerá em tracebacks e mensagens relacionados ao objeto compilado.
py_compile.compile(
"src/pacote/modulo.py",
dfile="/app/pacote/modulo.py",
doraise=True,
)Isso é útil quando o caminho no ambiente de build difere do caminho de implantação. Use uma localização significativa e não exponha diretórios secretos.
Nível de otimização
optimize é passado ao built-in compile(). O padrão -1 utiliza o nível do interpretador atual.
py_compile.compile(
"modulo.py",
optimize=1,
doraise=True,
)O nível 1 remove asserts e altera __debug__. O nível 2 também remove docstrings em muitos contextos. Não use otimização para esconder código nem presuma ganho significativo sem medir.
Arquivos diferentes por otimização
A tag do cache inclui a variante de otimização quando o destino padrão é usado. Isso permite que caches de níveis diferentes coexistam em __pycache__.
Ao escolher cfile manualmente, não sobrescreva acidentalmente variantes necessárias. Registre versão, implementação e otimização no processo de build.
Modos de invalidação
PycInvalidationMode controla como o interpretador verifica se um .pyc está atualizado.
TIMESTAMP: compara timestamp e tamanho;CHECKED_HASH: armazena hash e compara com o conteúdo;UNCHECKED_HASH: armazena hash, mas confia que um sistema externo mantém o cache.
O modo fica registrado no cabeçalho do arquivo.
Invalidação por timestamp
from py_compile import PycInvalidationMode
py_compile.compile(
"modulo.py",
doraise=True,
invalidation_mode=PycInvalidationMode.TIMESTAMP,
)É rápido e padrão quando SOURCE_DATE_EPOCH não está definido. Filesystems com resolução de tempo limitada podem criar casos em que conteúdo muda, mas metadados parecem equivalentes.
Hash verificado
py_compile.compile(
"modulo.py",
doraise=True,
invalidation_mode=PycInvalidationMode.CHECKED_HASH,
)O interpretador calcula novamente o hash da fonte durante a importação. Esse modo melhora determinismo e evita depender somente de timestamps, com custo adicional de leitura e hashing.
Hash não verificado
py_compile.compile(
"modulo.py",
doraise=True,
invalidation_mode=PycInvalidationMode.UNCHECKED_HASH,
)Nesse modo, o runtime assume que o arquivo está correto. Use somente quando um gerenciador de pacotes ou build system controla rigorosamente a atualização dos caches.
SOURCE_DATE_EPOCH
Quando a variável SOURCE_DATE_EPOCH está presente, o padrão do modo de invalidação passa a ser CHECKED_HASH. Isso ajuda builds reproduzíveis que não querem depender do horário real.
Desde Python 3.7.2, a variável determina o padrão, mas não substitui um argumento explícito.
quiet
quiet controla mensagens quando doraise=False.
- 0 ou 1: comportamento normal de diagnóstico;
- 2: nenhuma mensagem e
doraisedeixa de ter efeito.
Em código de automação, prefira doraise=True e captura de exceção em vez de silenciar completamente a falha.
Interface de linha de comando
O módulo pode compilar arquivos informados explicitamente.
python -m py_compile arquivo1.py arquivo2.pyEle não procura recursivamente por fontes. O status de saída é diferente de zero se algum arquivo não puder ser compilado.
Ler caminhos da entrada padrão
Quando - é o único argumento, os nomes dos arquivos são lidos de stdin.
find src -name '*.py' -print | python -m py_compile -Caminhos com newline são raros, mas possíveis em sistemas Unix. Para pipelines rigorosos, use uma integração Python que percorra Path diretamente.
Modo quiet na CLI
python -m py_compile -q arquivo1.py arquivo2.pyA opção reduz mensagens, mas o código de saída ainda deve ser verificado pelo pipeline.
Permissões e instalação compartilhada
A compilação antecipada é útil quando usuários finais podem ler o pacote, mas não possuem permissão para gravar em __pycache__.
O processo de instalação executado com privilégios adequados cria os caches. Depois, permissões devem permitir leitura sem conceder escrita desnecessária.
Concorrência
A escrita usa arquivo temporário e renomeação, o que reduz resultados parciais quando vários processos compilam o mesmo destino. Isso não elimina toda disputa de build nem torna seguro escolher o mesmo cfile para fontes diferentes.
Use diretórios separados por versão e evite builds concorrentes que escrevem no mesmo artefato personalizado.
pyc não protege o código
Bytecode pode ser inspecionado e desmontado. Distribuir apenas .pyc não fornece proteção forte de propriedade intelectual, assinatura ou criptografia.
Além disso, código não confiável continua perigoso quando importado, independentemente de estar em fonte ou bytecode.
Remover caches antigos
Não é necessário versionar __pycache__ na maioria dos projetos. Ao trocar interpretador ou criar um build limpo, remova caches e regenere.
from pathlib import Path
for pasta in Path(".").rglob("__pycache__"):
for arquivo in pasta.iterdir():
arquivo.unlink()
pasta.rmdir()Use ferramentas de limpeza com cuidado para não apagar ambientes compartilhados.
Exemplo de compilador controlado
from pathlib import Path
import py_compile
def compilar(arquivo: Path) -> Path:
destino = py_compile.compile(
str(arquivo),
doraise=True,
optimize=0,
invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
)
return Path(destino)O chamador pode registrar tamanho, hash do artefato e versão do Python.
Testes
Crie arquivos temporários válidos e inválidos e confirme destino, exceções e modo escolhido.
from tempfile import TemporaryDirectory
with TemporaryDirectory() as pasta:
fonte = Path(pasta) / "ok.py"
fonte.write_text("valor = 42\n", encoding="utf-8")
pyc = compilar(fonte)
assert pyc.exists()Teste também symlink de destino em plataformas que oferecem suporte.
Erros frequentes
- Presumir que pyc é portátil entre versões.
- Ignorar o retorno
Nonequandodoraise=False. - Escolher um
cfilecompartilhado por várias fontes. - Usar hash não verificado sem build confiável.
- Distribuir pyc como proteção do código.
- Confundir compilação com execução de testes.
- Não registrar o nível de otimização.
- Versionar caches gerados localmente sem necessidade.
Boas práticas
- Use
doraise=Trueem automação. - Prefira o caminho padrão de
__pycache__. - Escolha explicitamente o modo em builds reproduzíveis.
- Separe artefatos por versão e otimização.
- Compile antes da implantação para detectar sintaxe inválida.
- Execute testes além da compilação.
- Não importe bytecode não confiável.
- Limpe caches ao trocar de ambiente.
Conclusão
O módulo py_compile no Python compila arquivos-fonte em caches .pyc, permitindo validar sintaxe e preparar instalações onde o runtime não pode escrever no diretório do pacote. Ele oferece controle sobre destino, nome em traceback, otimização e invalidação.
A escolha entre timestamp e hash depende do processo de build. Com exceções explícitas, caminhos padronizados e isolamento por versão, py_compile torna a geração de bytecode previsível sem confundir cache com binário portátil, proteção de código ou teste funcional.







