Pacotes Python e o Sistema de Importação
Aprenda como funcionam os pacotes Python: crie um pacote com __init__.py, use importações absolutas e relativas, exponha uma API pública limpa e evite erros comuns.
Um pacote é um diretório de módulos Python que você trata como uma única unidade importável. Enquanto um módulo é um único arquivo .py, um pacote é uma pasta — potencialmente contendo muitos módulos e sub-pacotes — que o sistema de importação do Python pode navegar como uma árvore. Este capítulo explica como criar pacotes, controlar o que eles expõem, escrever importações absolutas e relativas corretamente, e evitar as armadilhas que costumam pegar iniciantes de surpresa.
Módulos vs. Pacotes — a Diferença Principal
Um módulo é um único arquivo .py:
greetings.py ← moduleUm pacote é um diretório que contém pelo menos um arquivo especial chamado __init__.py:
greetings/ ← package
__init__.py
english.py
spanish.pyAmbos são importados com a mesma palavra-chave import, mas um pacote oferece uma hierarquia de namespaces: greetings.english e greetings.spanish são módulos separados, mas compartilham o namespace greetings.
Quando usar um módulo vs. um pacote:
| Situação | Use |
|---|---|
| Um utilitário pequeno e independente | Módulo (um arquivo .py) |
| Vários módulos relacionados que você quer sob um único nome | Pacote (um diretório) |
| Uma biblioteca que você pretende distribuir no PyPI | Pacote (com layout src/) |
O Arquivo __init__.py
__init__.py é o que transforma um diretório em um pacote. O Python o executa na primeira vez que o pacote (ou qualquer um de seus módulos) é importado. Pode estar vazio, ou pode:
- Importar nomes de sub-módulos para torná-los disponíveis no nível do pacote
- Executar a inicialização no nível do pacote (configuração de logging, verificações de versão, etc.)
- Definir
__all__para controlarfrom package import *
Layout mínimo de um pacote
myapp/
__init__.py ← can be empty
utils.py
config.py# myapp/__init__.py (empty — that is fine)# myapp/utils.py
def greet(name):
return f"Hello, {name}!"Importar de fora do pacote:
from myapp.utils import greet
print(greet("Alice")) # Hello, Alice!Expondo nomes no nível do pacote
Um padrão comum é importar os nomes mais usados no __init__.py para que os chamadores possam escrever from myapp import greet em vez de from myapp.utils import greet.
# myapp/__init__.py
from .utils import greet
from .config import MAX_RETRIESAgora ambos os nomes estão disponíveis diretamente no pacote:
import myapp
print(myapp.greet("Bob")) # Hello, Bob!
print(myapp.MAX_RETRIES) # whatever config.py definesImportações Absolutas
Uma importação absoluta sempre começa pelo pacote de nível superior ou por um diretório em sys.path. Ela nunca depende de onde está o arquivo que realiza a importação.
project/
myapp/
__init__.py
utils.py
services/
__init__.py
email.pyDentro de email.py, uma importação absoluta tem a seguinte forma:
# myapp/services/email.py
from myapp.utils import greet # absolute — starts from the top-level package
def send_welcome(user):
message = greet(user)
print(f"Sending: {message}")As importações absolutas são o estilo padrão e recomendado (PEP 8). Elas são inequívocas independentemente de como o Python é invocado.
Importações Relativas
Uma importação relativa usa pontos (.) para navegar pela árvore do pacote em relação à localização do arquivo atual.
.significa o pacote atual..significa o pacote pai...significa o pacote avô, e assim por diante
# myapp/services/email.py
# One dot — import from myapp.services (same directory)
from . import sms
# Two dots — import from myapp (parent directory)
from ..utils import greetQuando usar importações relativas
As importações relativas são úteis dentro de um pacote quando você quer deixar claro que greet vem deste pacote e não de alguma biblioteca externa com o mesmo nome. Elas também facilitam a refatoração porque as importações acompanham o pacote caso você renomeie o diretório de nível superior.
Atenção: as importações relativas só funcionam dentro de um pacote. Se você executar python myapp/utils.py diretamente, o Python o trata como um script independente, não como parte de um pacote, e uma importação relativa lança ImportError: attempted relative import with no known parent package. Execute o pacote com python -m myapp.utils em vez disso.
# Wrong — runs utils.py as a script, breaking relative imports
$ python myapp/utils.py
# Right — runs utils.py as part of the myapp package
$ python -m myapp.utilsControlando a API Pública com __all__
__all__ é uma lista de nomes que from package import * exporta. Ela também documenta o que o pacote considera público.
# myapp/__init__.py
from .utils import greet, farewell
from .config import MAX_RETRIES
__all__ = ["greet", "MAX_RETRIES"] # farewell is intentionally not exportedAgora from myapp import * traz apenas greet e MAX_RETRIES. A função farewell ainda existe; ela simplesmente não faz parte da interface pública anunciada. Nomes prefixados com um único sublinhado (_private) também são excluídos de import * por convenção.
Pacotes Aninhados (Sub-pacotes)
Pacotes podem conter outros pacotes. Cada subdiretório precisa de seu próprio __init__.py.
analytics/
__init__.py
reports/
__init__.py
daily.py
weekly.py
charts/
__init__.py
bar.py
pie.pyImporte um módulo profundamente aninhado usando o caminho completo com pontos:
from analytics.reports.daily import generate_report
from analytics.charts.bar import BarChartOu, se analytics/__init__.py os expõe:
# analytics/__init__.py
from .reports.daily import generate_report# caller
from analytics import generate_reportQual profundidade é adequada?
Um pacote com três ou quatro níveis de profundidade geralmente é sinal de que cresceu demais e deve ser dividido em pacotes separados de nível superior (instaláveis independentemente). Para a maioria dos projetos, dois níveis (package.module) são suficientes.
Um Exemplo Prático: Construindo um Pacote geometry
Vamos construir um pacote pequeno mas realista, passo a passo.
Layout de diretório
geometry/
__init__.py
shapes.py
conversions.pyshapes.py
# geometry/shapes.py
import math
def circle_area(radius):
"""Return the area of a circle with the given radius."""
if radius < 0:
raise ValueError("radius must be non-negative")
return math.pi * radius ** 2
def rectangle_area(width, height):
"""Return the area of a rectangle."""
return width * height
def triangle_area(base, height):
"""Return the area of a triangle."""
return 0.5 * base * heightconversions.py
# geometry/conversions.py
def degrees_to_radians(degrees):
"""Convert degrees to radians."""
import math
return degrees * math.pi / 180
def radians_to_degrees(radians):
"""Convert radians to degrees."""
import math
return radians * 180 / math.pi__init__.py — expondo os nomes principais
# geometry/__init__.py
"""
geometry — simple 2-D geometry utilities.
Public API:
circle_area(radius) -> float
rectangle_area(width, height) -> float
triangle_area(base, height) -> float
degrees_to_radians(degrees) -> float
radians_to_degrees(radians) -> float
"""
from .shapes import circle_area, rectangle_area, triangle_area
from .conversions import degrees_to_radians, radians_to_degrees
__all__ = [
"circle_area",
"rectangle_area",
"triangle_area",
"degrees_to_radians",
"radians_to_degrees",
]Usando o pacote
# main.py (sits next to the geometry/ directory)
import geometry
print(geometry.circle_area(5)) # 78.53981633974483
print(geometry.rectangle_area(4, 6)) # 24
print(geometry.degrees_to_radians(90)) # 1.5707963267948966Ou com importações seletivas:
from geometry import circle_area, degrees_to_radians
print(circle_area(3)) # 28.274333882308138
print(degrees_to_radians(180)) # 3.141592653589793Pacotes de Namespace (Python 3.3+)
Desde o Python 3.3, um diretório sem __init__.py é um pacote de namespace. O Python mescla todos os diretórios com o mesmo nome em sys.path em um único pacote lógico. Isso é principalmente útil para grandes organizações que dividem um único pacote entre vários repositórios ou diretórios de instalação.
Para o desenvolvimento cotidiano, sempre inclua __init__.py. Isso deixa sua intenção inequívoca e funciona em todas as versões do Python.
Como o Python Encontra Pacotes
Quando você escreve import geometry, o Python pesquisa sys.path em ordem:
- O diretório do script em execução (ou o diretório atual no modo interativo)
- Diretórios na variável de ambiente
PYTHONPATH - Os diretórios da biblioteca padrão
- O diretório
site-packages(onde ficam os pacotes instalados pelo pip)
import sys
print(sys.path)O diretório do pacote deve estar diretamente dentro de um desses locais. Se geometry/ estiver em /home/alice/projects/, o Python não o encontrará, a menos que /home/alice/projects/ esteja em sys.path.
Dica: use um ambiente virtual e instale seu pacote em modo de desenvolvimento (pip install -e .) para que o Python sempre o encontre sem manipulação manual de sys.path.
Distribuindo um Pacote
Para compartilhar um pacote com outras pessoas (ou instalá-lo com pip), você precisa de um pyproject.toml na raiz do projeto:
my_project/
pyproject.toml ← build metadata
src/
geometry/
__init__.py
shapes.py
conversions.pyUm pyproject.toml mínimo:
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.backends.legacy:build"
[project]
name = "geometry"
version = "0.1.0"
description = "Simple 2-D geometry utilities"
requires-python = ">=3.9"Instale localmente em modo editável durante o desenvolvimento:
pip install -e .Agora import geometry funciona em qualquer lugar no seu ambiente virtual, independentemente do diretório atual.
Erros Comuns
__init__.py ausente
Se você esquecer o __init__.py, o Python 3 trata o diretório como um pacote de namespace (o que geralmente ainda funciona), mas o Python 2 o ignora completamente. Seja explícito: sempre adicione __init__.py.
Nomear um pacote igual a um módulo da stdlib
Evite nomes como math/, json/, os/, email/. O Python pode importar seu pacote em vez do da biblioteca padrão, quebrando código não relacionado.
Executar um módulo de pacote como script
Como mencionado acima, executar python myapp/services/email.py diretamente quebra as importações relativas. Use python -m myapp.services.email em vez disso.
Importações circulares entre módulos do mesmo pacote
Se shapes.py importa de conversions.py e conversions.py importa de shapes.py, você tem uma importação circular. Os sintomas incluem ImportError ou nomes que aparecem inesperadamente como None. A solução geralmente é mover a lógica compartilhada para um terceiro módulo, ou atrasar a importação para dentro do corpo de uma função.
# Delayed import — breaks the cycle at module load time
def some_function():
from .shapes import circle_area # imported only when the function is called
...ImportError ao usar importações relativas fora de um pacote
# Will raise: ImportError: attempted relative import with no known parent package
# if run as: python myapp/utils.py
from . import config # relative import inside utils.pyExecute com python -m myapp.utils ou reestruture para que o ponto de entrada seja um script separado que importe o pacote.
Resumo
| Conceito | Resumo |
|---|---|
| Pacote | Um diretório com __init__.py contendo módulos |
__init__.py | Transforma um diretório em pacote; executado na primeira importação |
| Importação absoluta | from myapp.utils import greet — sempre a partir da raiz |
| Importação relativa | from ..utils import greet — relativa ao arquivo atual |
__all__ | Lista nomes exportados por from package import * |
| Pacote de namespace | Diretório sem __init__.py; apenas Python 3.3+ |
| Instalação editável | pip install -e . — pacote encontrado em qualquer lugar no venv |
Veja Python Modules para organização de código em arquivo único, Python pip para instalar pacotes de terceiros, e Python Virtual Environments para manter as dependências do projeto isoladas.