PyInstaller: como transformar um script Python em EXE no Windows
Transforme scripts Python em executáveis .exe com PyInstaller: instalação, modo onefile x onedir, arquivo .spec, ícone, dados extras, falso positivo de antivírus e alternativas como Nuitka e cx_Freeze.
Para transformar um script Python em EXE, instale o PyInstaller (pip install pyinstaller) e rode pyinstaller --onefile --name meuapp main.py; o executável aparece em dist/meuapp.exe e roda em qualquer Windows 10/11 sem Python instalado. É o caminho mais direto para entregar uma automação, um conversor de planilhas ou um pequeno utilitário interno para colegas e clientes que não são programadores — cenário comum no dia a dia de quem faz automação com Python no Brasil.
Quem pergunta a um assistente “como transformar meu script Python em exe para rodar no computador do cliente?” precisa de quatro respostas na mesma página: o comando exato, como resolver os problemas clássicos (módulo faltando, antivírus reclamando, executável gigante), como versionar o build em um arquivo .spec e quando trocar o PyInstaller por uma alternativa. É o que este guia cobre, com foco em Windows — a plataforma da maioria dos usuários finais brasileiros de automações.
Como o PyInstaller funciona (e o que ele não faz)
O PyInstaller analisa as importações do seu programa, copia o interpretador CPython, as bibliotecas e seus arquivos para um bundle, e gera um pequeno executável que inicializa esse ambiente e executa seu código. O usuário final não precisa instalar nada.
O que importa entender:
- Não é compilação nativa. O bytecode continua bytecode — dentro do pacote há um interpretador Python completo. Não espere ganho de performance.
- O código-fonte permanece extraível. Ferramentas conseguem recuperar o bytecode do bundle. PyInstaller resolve distribuição, não proteção de propriedade intelectual.
- Cada build serve um sistema operacional. Não existe cross-compile: build de Windows gera EXE para Windows, feito no Windows. Para binários Linux, gere no Linux (ou num container Docker, como no guia de Python com Docker).
Preparação: ambiente virtual e um app mínimo
Sempre gere o executável a partir de um ambiente virtual limpo, com apenas as dependências que o app realmente usa — o PyInstaller copia tudo que está instalado, e um ambiente poluído gera executáveis de centenas de megabytes.
python -m venv .venv
.venv\Scripts\activate
pip install pyinstaller "openpyxl>=3.1"
pip freeze > requirements.txt
Se você usa uv, o equivalente é uv venv, uv pip install pyinstaller openpyxl e ativar .venv\Scripts\activate.
App de exemplo, main.py — um conversor de planilhas simples:
import sys
from pathlib import Path
from openpyxl import load_workbook
def converter(caminho_xlsx: str) -> None:
planilha = load_workbook(caminho_xlsx, read_only=True, data_only=True)
destino = Path(caminho_xlsx).with_suffix(".csv")
with destino.open("w", encoding="utf-8", newline="") as arquivo:
for linha in planilha.active.iter_rows(values_only=True):
arquivo.write(";".join("" if c is None else str(c) for c in linha) + "\n")
print(f"Convertido: {destino}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Uso: converter.exe planilha.xlsx")
sys.exit(1)
converter(sys.argv[1])
Primeiro build: --onefile e --onedir
Com o ambiente ativo, na pasta do projeto:
pyinstaller --onefile --name converter main.py
Resultado:
dist\converter.exe— o executável único (modo onefile). Fácil de enviar por e-mail ou pendrive.build\— arquivos temporários do build (pode apagar).converter.spec— a “receita” do build, reutilizável.
A opção --onefile empacota tudo em um EXE único. Sem ela, o PyInstaller gera o modo onedir: uma pasta com o EXE e as DLLs/bibliotecas ao lado. Qual escolher:
| Critério | --onefile | --onedir (padrão) |
|---|---|---|
| Distribuição | Um arquivo só, fácil de enviar | Pasta/ZIP com dezenas de arquivos |
| Inicialização | Mais lenta (extrai para temp a cada execução) | Instantânea |
| Antivírus | Mais falso positivo | Menos suspeito |
| Atualização | Trocar um arquivo | Trocar bibliotecas individuais |
| Ideal para | Utilitários pequenos enviados ao cliente | Apps maiores, uso frequente |
Regra prática para automações internas: prefira --onedir e distribua um ZIP — é mais rápido de abrir e menos rejeitado por antivírus. Use --onefile quando enviar um arquivo único é um requisito do fluxo (download de portal, e-mail com anexo único).
Ícone, nome e metadados
Um ícone próprio faz o utilitário parecer um produto, não um script:
pyinstaller --onefile --name converter ^
--icon=icones\logo.ico ^
--version-file=versao.txt ^
main.py
O --version-file (arquivo texto no formato do Windows) define versão, empresa e descrição que aparecem nas propriedades do EXE:
# versao.txt (formato de versão do Windows)
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(1, 2, 0, 0),
prodvers=(1, 2, 0, 0),
),
kids=[
StringFileInfo([
StringTable('040904B0', [
StringStruct('CompanyName', 'Minha Empresa LTDA'),
StringStruct('FileDescription', 'Conversor de planilhas para CSV'),
StringStruct('FileVersion', '1.2.0.0'),
StringStruct('ProductName', 'Conversor'),
])
])
]
)
Converta PNG para .ico com Pillow, incluindo múltiplos tamanhos (256/128/64/48/32/16 px) para o Windows não renderizar ícone borrado:
from PIL import Image
imagem = Image.open("logo.png")
imagem.save("icones/logo.ico", sizes=[(256, 256), (128, 128), (64, 64), (48, 48), (32, 32), (16, 16)])
O arquivo .spec: versionando o build
Depois do primeiro build, o arquivo converter.spec contém tudo o que o PyInstaller decidiu (análises, hidden imports, datas, ícones). Em vez de repetir flags longas na linha de comando, versione o .spec no repositório e gere builds com:
pyinstaller converter.spec
Um .spec típico:
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
["main.py"],
pathex=[],
binaries=[],
datas=[("templates", "templates")], # (origem, destino dentro do bundle)
hiddenimports=[],
excludes=["tkinter", "matplotlib", "pytest"],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
name="converter",
icon="icones/logo.ico",
console=True,
)
Dois campos resolvem a maioria dos problemas:
datas: inclui arquivos que não são código (configurações, modelos de documento, imagens). Dentro do app, localize-os comsys._MEIPASSno modo onefile:
from pathlib import Path
import sys
def caminho_recurso(relativo: str) -> Path:
base = Path(getattr(sys, "_MEIPASS", Path(__file__).parent))
return base / relativo
excludes: remove módulos que a análise puxou sem necessidade (por exemplotkinteroumatplotlibem um app de linha de comando), encolhendo o EXE de forma expressiva.
Os 5 problemas clássicos (e a solução)
1. “ModuleNotFoundError” só no EXE. Imports dinâmicos (importlib, plugins, imports dentro de funções para dependências opcionais) não são detectados pela análise. Adicione em hiddenimports no .spec — ou --hidden-import=pandas.plotting._matplotlib na linha de comando — e gere o build de novo.
2. Executável gigante. Causa nº 1: ambiente virtual com pacotes a mais (NumPy, Pandas e PySide pesam dezenas de MB cada). Crie um ambiente novo só para o build. Causa nº 2: análise puxando test/CI — use excludes. O comando pyi-archive_viewer dist\converter.exe lista o que entrou no bundle.
3. Antivírus ou SmartScreen bloqueando. Executáveis novos sem assinatura disparam heurísticas. Mitigue com o build mais recente do PyInstaller (versões antigas têm bootloads banidos), prefira --onedir, e publique junto o checksum SHA-256 para o cliente conferir:
Get-FileHash .\dist\converter.exe -Algorithm SHA256
A solução definitiva é assinar o EXE com um certificado de assinatura de código — obrigatório para distribuição ampla, opcional para uso interno.
4. App abre e fecha na hora. É uma exceção estourando antes de qualquer input(). Rode o EXE pelo cmd para ver o traceback: dist\converter.exe dentro de um terminal. Para apps com interface, registre a exceção em arquivo:
import logging
import tempfile
logging.basicConfig(
filename=tempfile.gettempdir() + r"\converter.log",
level=logging.ERROR,
)
5. Windows Defender deleta o arquivo na máquina do cliente. Comum com --onefile e autoupdate embutido. Além do que foi dito no item 3, considere distribuir via pip install de um pacote interno — o guia de publicar pacote no PyPI mostra o caminho — e reservar o EXE para quem não pode usar Python.
PyInstaller ou alternativas? Tabela de decisão
| Ferramenta | O que faz | Melhor quando | Atenção |
|---|---|---|---|
| PyInstaller | Empacota interpretador + código | Caso geral, maior suporte da comunidade | EXE grande, código extraível |
| Nuitka | Compila Python para C | Performance e proteção de código | Build lento; exige compilador C |
| cx_Freeze | Empacota via setup script | Alternativa quando PyInstaller falha | Menos automático na análise |
| pynsist | Instalador MSI com Python embutido | Instaladores de desktop Windows | Menos usado em projetos novos |
| pip + venv | Distribui código, não binário | Público técnico | Exige instalar Python |
Para ferramentas de linha de comando direcionadas a pessoas desenvolvedoras, o melhor “executável” costuma ser um CLI instalável com pip, não um EXE. Reserve o empacotamento para usuários não técnicos. Se o gargalo é performance em vez de distribuição, o problema pode ser o algoritmo — ou a hora de considerar extensões nativas com Rust e PyO3.
Checklist antes de enviar para o cliente
- Build gerado em ambiente virtual limpo, a partir do
.specversionado. - Testado em uma máquina (ou VM) sem Python instalado.
- Versão e descrição preenchidas via
--version-file. - Checksum SHA-256 publicado junto do download.
- Log de erros gravando em arquivo (e você sabendo onde procurar).
- Caminho de rollback: manter a versão anterior do EXE disponível.
Conclusão
O PyInstaller resolve a última milha de todo projeto de automação: fazer o código chegar a quem precisa dele sem exigir que a pessoa instale Python, Git ou dependências. Comece com pyinstaller --onefile main.py, evolua para um .spec versionado com hiddenimports e excludes, e trate antivírus e assinatura como parte do produto, não como detalhe. Quando o limite do empacotamento aparecer — performance, proteção de código, instaladores profissionais — a tabela de decisão acima mostra o próximo passo.