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.

24 Sep 2026 7 min de leitura Equipe Python Dev BR

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çãoUm arquivo só, fácil de enviarPasta/ZIP com dezenas de arquivos
InicializaçãoMais lenta (extrai para temp a cada execução)Instantânea
AntivírusMais falso positivoMenos suspeito
AtualizaçãoTrocar um arquivoTrocar bibliotecas individuais
Ideal paraUtilitários pequenos enviados ao clienteApps 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 com sys._MEIPASS no 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 exemplo tkinter ou matplotlib em 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

FerramentaO que fazMelhor quandoAtenção
PyInstallerEmpacota interpretador + códigoCaso geral, maior suporte da comunidadeEXE grande, código extraível
NuitkaCompila Python para CPerformance e proteção de códigoBuild lento; exige compilador C
cx_FreezeEmpacota via setup scriptAlternativa quando PyInstaller falhaMenos automático na análise
pynsistInstalador MSI com Python embutidoInstaladores de desktop WindowsMenos usado em projetos novos
pip + venvDistribui código, não binárioPúblico técnicoExige 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

  1. Build gerado em ambiente virtual limpo, a partir do .spec versionado.
  2. Testado em uma máquina (ou VM) sem Python instalado.
  3. Versão e descrição preenchidas via --version-file.
  4. Checksum SHA-256 publicado junto do download.
  5. Log de erros gravando em arquivo (e você sabendo onde procurar).
  6. 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.

Leia também

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados