O Modelo de Empacotamento Python: Da Árvore de Código Fonte a um Pacote Instalado
Cada página nesta seção - build backends, versionamento, entry points, extensões nativas, publicação - na verdade descreve um único pipeline sob diferentes ângulos. Uma árvore de código fonte se torna um artefato distribuível através de uma etapa de build padronizada, esse artefato carrega metadados descrevendo o que ele precisa e fornece, e um instalador o coloca em um ambiente para que import meuapp funcione. Entender esse pipeline como um sistema único, em vez de uma coleção de ferramentas memorizadas separadamente, é o que faz o restante dos detalhes desta seção serem compreendidos.
Esta página detalha esse pipeline: o que um build backend realmente faz, por que o Python distribui dois formatos de distribuição em vez de um, o que as tags no nome de um arquivo wheel significam e por que elas existem, e onde o versionamento, os entry points e o código nativo se encaixam na mesma imagem que Noções Básicas de Empacotamento e Build Backends já introduzem na prática.
Resumo
- Empacotamento é um pipeline - árvore de código fonte, build backend, artefato de distribuição (sdist ou wheel), instalador - e cada ferramenta específica nesta seção é uma peça desse único pipeline.
- Por Que Importa: Tratar o empacotamento como várias ferramentas não relacionadas (uma ferramenta de build, uma ferramenta de publicação, uma convenção de versionamento) esconde por que elas precisam concordar entre si e por que um pipeline quebrado falha de maneiras confusas e não locais.
- Conceitos Chave: build backend, sdist, wheel, tag de plataforma/ABI, PEP 517, entry point.
- Quando Usar: Decidir qual backend configurar, entender por que um wheel construído no Linux não instala no Windows, ou diagnosticar por que um pacote instalado está faltando arquivos.
- Limitações / Trade-offs: A padronização que este pipeline fornece (PEP 517/518) não elimina a complexidade subjacente do código nativo - apenas dá a essa complexidade uma interface consistente.
- Tópicos Relacionados: resolução de dependências, ambientes virtuais, compilação de extensões nativas, integração contínua para artefatos de release.
Fundamentos
Um pacote Python, no sentido de distribuição, começa como uma árvore de código fonte comum com um pyproject.toml em sua raiz.
A tabela [build-system] desse arquivo nomeia um build backend - hatchling, setuptools, poetry-core, maturin, e outros - que é a peça de software realmente responsável por transformar essa árvore de código fonte em um artefato distribuível.
Essa divisão é importante porque pip e uv não sabem como construir seu projeto específico; eles sabem como chamar o backend que você declarou, seguindo uma interface padrão, e deixar que ele faça o trabalho real.
Essa interface padrão é o PEP 517: qualquer backend que implemente um pequeno conjunto de funções obrigatórias (construir um wheel, construir um sdist) pode ser trocado sem que o instalador precise de lógica específica do backend, que é por que mudar de setuptools para hatchling é uma alteração de configuração, não uma reescrita de como você instala coisas.
Existem dois artefatos de distribuição que um backend pode produzir, e eles resolvem problemas diferentes.
Um sdist (source distribution) é um snapshot da sua árvore de código fonte, mais metadados suficientes para construí-lo posteriormente - é portátil entre plataformas porque ainda não foi construído.
Um wheel é o oposto: um artefato pré-construído, pronto para instalação, organizado exatamente como deve aparecer em site-packages, que é por que instalar a partir de um wheel é rápido - não há etapa de build no momento da instalação.
Mecânicas e Interações
O build backend é invocado, conceitualmente, em dois momentos diferentes, e confundi-los é uma fonte comum de bugs de empacotamento.
Tempo de build é quando uv build ou python -m build chama o backend para produzir um sdist e/ou wheel a partir da sua árvore de código fonte; é aqui que a configuração do tipo [tool.hatch.build] (quais arquivos incluir, quais excluir, onde os pacotes residem) realmente entra em vigor.
Tempo de instalação é quando pip ou uv pega um wheel já construído e copia seu conteúdo para o site-packages de um ambiente, atualizando os metadados desse ambiente para que o pacote seja importável; para uma instalação de wheel puro, o backend não está envolvido em nada nesta etapa.
Uma instalação editável (pip install -e . ou uv pip install -e .) confunde essa distinção deliberadamente - ela registra sua árvore de código fonte como importável no local, sem produzir um wheel real, especificamente para que o desenvolvimento local não exija uma reconstrução após cada alteração.
Nomes de arquivos de wheel codificam um contrato que explica um ponto comum de confusão: por que um wheel construído em uma máquina às vezes se recusa a instalar em outra.
myapp-1.2.0-cp313-cp313-manylinux_2_28_x86_64.whl
│ │ │ │
│ │ │ └─ tag de plataforma: SO + arquitetura que este wheel tem como alvo
│ │ └─ tag ABI: interface binária com a qual foi compilado
│ └─ tag Python: interpretador/versão que tem como alvo (cp313 = CPython 3.13)
└─ versão do pacoteUm wheel puramente Python tipicamente carrega py3-none-any nesses campos, o que significa que ele não se importa com a versão do interpretador, ABI ou plataforma - é apenas código fonte Python, então instala em qualquer lugar.
Um wheel contendo código nativo compilado, por outro lado, só é válido para a exata combinação de interpretador ABI e SO/arquitetura gravada nessas tags; isso não é uma limitação de empacotamento tanto quanto uma descrição honesta do que um binário compilado realmente é.
Considerações Avançadas e Aplicações
Tudo o que esta seção cobre além do loop principal de build/instalação são metadados ou processos sobrepostos ao mesmo pipeline, não um sistema separado.
Versionamento (Versionamento e Changelogs) são metadados que o build backend embuti no artefato para que instaladores e resolvedores de dependência possam comparar releases; SemVer é uma convenção sobre o que os números de versão devem significar, não um mecanismo que o pipeline impõe por conta própria.
Entry points (Entry Points e Console Scripts) também são metadados - um mapeamento de um nome de comando para uma função Python - que o build backend escreve no wheel, e que o instalador lê para gerar um script executável real no momento da instalação; o CLI que você digita em um terminal é gerado, não escrito à mão.
Extensões nativas (Native Extensions & Wheels) adicionam uma etapa de compilação antes da mesma saída em formato de wheel - o backend (frequentemente emparelhado com uma toolchain de compilador, ou um backend focado em Rust como maturin) produz arquivos .so/.pyd que pousam dentro do wheel assim como arquivos puramente Python fariam, marcados com a plataforma/ABI que a compilação teve como alvo. Construir para múltiplas plataformas significa executar essa etapa de compilação uma vez por alvo, que é o que ferramentas como cibuildwheel automatizam em vez de substituir.
Publicação (Publicação no PyPI) é a etapa final de mover um artefato já construído para um registro que os instaladores podem encontrar pelo nome; nada sobre a publicação muda como o artefato foi construído, que é por que testar um build localmente antes de publicar (instalar seu próprio wheel e importá-lo) captura a maioria dos erros de empacotamento antes que cheguem a um registro.
Distribuição de aplicativos (Distributing Applications) - PyInstaller, pipx, contêineres - existe em grande parte porque o pipeline de wheel assume que um interpretador e ambiente Python já existem na máquina de destino; quando essa suposição não se aplica (um usuário final sem Python instalado), o pipeline é envolvido em algo que agrupa um interpretador também.
| Forma de distribuição | O que assume | O que resolve | Custo |
|---|---|---|---|
| Sdist | Um build backend e toolchain disponíveis no tempo de instalação | Portabilidade entre plataformas e versões Python | Instalação mais lenta; requer um ambiente de build funcional |
| Wheel puro | Apenas uma versão Python compatível | Instalação rápida e sem build | Não cobre dependências compiladas |
| Wheel específico de plataforma | Uma combinação exata de interpretador ABI + SO + arquitetura | Instalação rápida com código nativo incluído | Deve ser construído separadamente por plataforma de destino |
| Aplicativo empacotado (PyInstaller, contêiner) | Nada - nenhum Python necessário no destino | Distribuição para usuários sem ambiente Python | Artefato muito maior; própria história de atualização/patch |
Raciocinar sobre qual linha se aplica a uma determinada decisão de release - "esta dependência tem que ser compilada por plataforma" ou "meu usuário final sequer tem Python" - é a chamada de julgamento real para a qual toda esta seção está construindo; as páginas de ferramentas específicas assumem que você pode colocar um problema nesta tabela antes de recorrer aos seus comandos.
Equívocos Comuns
- "pip constrói meu pacote." pip orquestra o processo, mas delega o build real para qualquer backend que
[build-system]declara; pip não sabe compilar uma extensão Cython ou organizar um wheel por conta própria. - "Um sdist e um wheel são apenas formatos de compressão diferentes da mesma coisa." Eles diferem no estágio do pipeline que representam - um sdist é código fonte pré-build, um wheel é um artefato finalizado e pronto para ser colocado - e um wheel pode conter arquivos (binários compilados) que nunca existiram na forma de código fonte do sdist.
- "Um wheel que funciona na minha máquina funcionará em qualquer caixa Linux." As tags de plataforma do wheel codificam suposições reais de ABI e SO; um wheel construído contra uma versão glibc ou arquitetura de CPU diferente pode falhar ao importar em uma máquina com uma diferente, que é exatamente o que as tags do tipo
manylinuxecibuildwheelexistem para gerenciar sistematicamente. - "Versionamento é apenas escolher um número." A string de versão é um metadado que outras ferramentas - resolvedores de dependência, changelogs, portões de release de CI - leem e agem sobre; uma versão inconsistente ou não monotônica pode quebrar a resolução para todos os consumidores do pacote, não apenas cosméticos.
- "Extensões nativas são um sistema de empacotamento completamente separado." Elas adicionam uma etapa de compilação antes que o mesmo formato de wheel que todo o resto usa; a saída ainda tem que satisfazer o mesmo contrato de tagging de plataforma/ABI que um wheel puramente Python.
FAQs
O que um build backend realmente faz que o pip não faz?
Ele contém a lógica específica do projeto para transformar uma árvore de código fonte em um wheel ou sdist - decidindo quais arquivos pertencem, compilando qualquer código nativo e escrevendo metadados do pacote; o pip só conhece a interface padrão (PEP 517) para chamar essa lógica, não como executá-la.
Por que o Python tem dois formatos de distribuição em vez de um?
Um sdist é um snapshot pré-build portátil útil quando um wheel ainda não existe para uma plataforma; um wheel é um artefato pré-construído e pronto para instalação que pula completamente a etapa de build - eles trocam portabilidade por velocidade de instalação em direções opostas.
O que os segmentos em um nome de arquivo de wheel significam?
Eles codificam a versão do pacote, a tag do interpretador Python, a ABI com a qual foi compilado e a plataforma/arquitetura que ele tem como alvo - um wheel puramente Python usa valores genéricos py3-none-any, enquanto um wheel compilado é fixado na combinação exata para a qual foi construído.
Por que não posso simplesmente instalar qualquer wheel que encontro em qualquer máquina?
Porque um wheel com código nativo compilado só é válido para a ABI do interpretador, SO e arquitetura gravados nas tags de seu nome de arquivo; instalá-lo em um alvo incompatível falha diretamente ou, pior, importa com sucesso, mas trava em tempo de execução.
Qual é a diferença entre tempo de build e tempo de instalação?
Tempo de build é quando o backend transforma o código fonte em um artefato e qualquer configuração de build específica do projeto entra em vigor; tempo de instalação é quando os arquivos de um wheel já construído são copiados para um ambiente - para um wheel puro, o backend não está envolvido no tempo de instalação.
Por que uma instalação editável não produz um wheel real?
Porque seu propósito é pular a etapa de build - ela registra sua árvore de código fonte como importável no local para que alterações locais sejam visíveis imediatamente sem reconstruir, o que é uma conveniência de desenvolvimento em vez de um artefato de distribuição.
Entry points e console scripts são um recurso separado do empacotamento?
Não - um entry point é um metadado que o backend escreve no wheel, e o instalador lê esses metadados para gerar um script executável real no momento da instalação, então o comando CLI que você executa é gerado a partir de metadados de empacotamento, não escrito à mão.
Adicionar uma extensão nativa muda o pipeline geral de empacotamento?
Não a forma do pipeline - ela adiciona uma etapa de compilação antes que o backend produza a mesma saída em formato de wheel, marcada com a plataforma/ABI que o código compilado realmente tem como alvo, que é por que pacotes nativos multiplataforma precisam ser construídos uma vez por alvo.
Por que eu precisaria de PyInstaller ou um contêiner em vez de apenas um wheel?
Um wheel assume que a máquina de destino já possui um interpretador e ambiente Python compatíveis; quando um usuário final não tem nenhum interpretador Python instalado, essa suposição falha, e todo o pipeline de wheel é envolvido em algo que agrupa um interpretador também.
O versionamento é imposto pelo próprio pipeline de empacotamento?
O pipeline embuti qualquer string de versão que você fornecer como metadado e os resolvedores comparam essas strings, mas o significado do número - convenções SemVer, ordenação de pré-release - é uma convenção que as equipes seguem, não algo que o build backend valida quanto à correção.
O que realmente quebra quando um build backend e um sdist discordam sobre os arquivos incluídos?
Um sdist que está faltando arquivos que o backend espera no tempo de build falhará ao reconstruir em outra máquina, e um wheel que está faltando arquivos que o backend esperava incluir produz um pacote que importa, mas está faltando módulos ou dados em tempo de execução - ambos são falhas na configuração de "o que pertence a este artefato", não do instalador.
Por que testar um wheel construído localmente antes de publicá-lo?
Porque a publicação apenas move um artefato já construído para um registro - ela não valida o conteúdo do artefato - então instalar seu próprio wheel em um ambiente novo e importá-lo captura arquivos ausentes ou metadados quebrados antes que cheguem a todos os futuros consumidores do pacote.
Relacionados
- Noções Básicas de Empacotamento - o ciclo de build/publicação na prática
- Build Backends - escolhendo e configurando hatchling, setuptools e alternativas
- Native Extensions & Wheels - a etapa de compilação antes do wheel
- Versionamento e Changelogs - os metadados que trafegam sobre o artefato
- Entry Points e Console Scripts - comandos CLI gerados a partir de metadados de empacotamento
- Publicação no PyPI - movendo um artefato construído para um registro
Versões da Stack: Esta página foi escrita para Python 3.14.0 (estável) / Python 3.13 (manutenção), com uv 0.6+ como a principal ferramenta de build/publicação referenciada.