---
title: "PyInstaller: como transformar um script Python em EXE no Windows"
url: "https://python.dev.br/blog/pyinstaller-transformar-script-python-exe/"
markdown_url: "https://python.dev.br/blog/pyinstaller-transformar-script-python-exe.MD"
description: "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."
date: "2026-09-24"
author: "Equipe Python Dev BR"
---

# 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](/blog/como-comecar-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](/blog/python-e-docker/)).

## Preparação: ambiente virtual e um app mínimo

Sempre gere o executável a partir de um [ambiente virtual](/guias/criando-virtual-environment/) 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.

```bash
python -m venv .venv
.venv\Scripts\activate
pip install pyinstaller "openpyxl>=3.1"
pip freeze > requirements.txt
```

Se você usa [uv](/blog/uv-gerenciador-pacotes-python/), o equivalente é `uv venv`, `uv pip install pyinstaller openpyxl` e ativar `.venv\Scripts\activate`.

App de exemplo, `main.py` — um conversor de [planilhas](/blog/python-para-automacao-de-planilhas/) simples:

```python
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:

```bash
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:

```bash
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:

```python
# 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:

```python
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:

```bash
pyinstaller converter.spec
```

Um `.spec` típico:

```python
# -*- 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:

```python
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:

```powershell
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:

```python
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](/guias/publicando-pacote-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](https://pyinstaller.org/) | 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](/blog/criando-cli-com-python/), 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](/blog/pyo3-rust-python-alta-performance/).

## 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

- [Automação com Python: por onde começar](/blog/como-comecar-com-python/)
- [Automação de planilhas com openpyxl](/blog/python-para-automacao-de-planilhas/)
- [Instalando Python no Windows](/guias/instalando-python-windows/)
- [uv: gerenciador de pacotes Python](/blog/uv-gerenciador-pacotes-python/)
- [Criando CLIs com Python](/blog/criando-cli-com-python/)
