Пакеты и структура проекта
Пакет — папка модулей
Когда модулей становится много, их группируют в пакеты — папки с файлом __init__.py:
Пакет — тоже модуль: объект с атрибутами, у которого есть __path__ (где искать подмодули).
import sys, tempfile, pathlib
root = pathlib.Path(tempfile.mkdtemp())
files = {
"shop/__init__.py": 'print(" init shop")\nVERSION = "1.0"\n',
"shop/payments.py": 'def charge(amount):\n return f"списано {amount} ₽"\n',
"shop/orders/__init__.py": 'print(" init shop.orders")\nfrom .service import place_order\n__all__ = ["place_order"]\n',
"shop/orders/models.py": 'class Order:\n def __init__(self, total):\n self.total = total\n',
"shop/orders/service.py": (
'from .models import Order # относительный: тот же пакет\n'
'from ..payments import charge # относительный: родительский пакет\n'
'def place_order(total):\n'
' order = Order(total)\n'
' return charge(order.total)\n'
),
}
for path, text in files.items():
(root / path).parent.mkdir(parents=True, exist_ok=True)
(root / path).write_text(text, encoding="utf-8")
sys.path.insert(0, str(root))
from shop.orders import place_order # __init__.py реэкспортирует функцию
import shop
print(place_order(990), shop.VERSION)
print(shop.__path__, [m for m in sys.modules if m.startswith("shop")])
Абсолютные и относительные импорты
# внутри shop/orders/service.py
from shop.payments import charge # абсолютный: полный путь от корня
from ..payments import charge # относительный: на уровень выше
from .models import Order # относительный: этот же пакет
| Абсолютный | Относительный | |
|---|---|---|
| Читаемость | сразу видно, откуда | короче внутри глубоких пакетов |
| Переименование пакета | нужно править импорты | не затрагивает |
Работает в скрипте python file.py |
да | нет |
| Рекомендация PEP 8 | предпочтителен | допустим внутри пакета |
Что писать в init.py
- Ничего — нормальный вариант (с Python 3.3 файл даже необязателен — тогда это namespace-пакет, но для обычных проектов
__init__.pyлучше оставить). - Публичный API: реэкспорт основных имён, чтобы пользователи писали
from shop import Order, а неfrom shop.orders.models import Order. __version__,__all__.
Не стоит: тяжёлые вычисления, подключения к базе, импорт всех подмодулей «на всякий случай» — это замедляет каждый импорт пакета.
Раскладка проекта
Современный стандарт — src-layout:
Зачем src/: из корня репозитория нельзя случайно импортировать myapp «как папку». Тесты работают с установленным пакетом (pip install -e .), и если вы забыли включить файл в пакет — тесты это поймают.
Плоская раскладка (myapp/ прямо в корне) проще и тоже распространена — например, в Django-проектах.
Точки входа
# src/myapp/__main__.py
from myapp.cli import main
main()
Теперь python -m myapp запускает приложение. А в pyproject.toml можно объявить консольную команду:
[project.scripts]
myapp = "myapp.cli:main" # после установки появится команда `myapp`
Типичные ошибки импорта
| Ошибка | Частая причина | Решение |
|---|---|---|
ModuleNotFoundError: No module named 'myapp' |
запуск не из того каталога, пакет не установлен, не активировано окружение | pip install -e ., python -m, активировать venv |
attempted relative import with no known parent package |
файл с относительными импортами запущен как скрипт | python -m package.module |
cannot import name 'X' (most likely due to a circular import) |
циклический импорт | вынести общее, импорт внутри функции |
module 'random' has no attribute 'randint' |
свой файл random.py перекрыл стандартный |
переименовать файл, удалить __pycache__ |
No module named 'requests' |
пакет установлен в другое окружение | python -m pip install requests тем же интерпретатором |
Какой Python?
which python / python -c "import sys; print(sys.executable)" показывают, какой интерпретатор запускается. Устанавливайте пакеты через python -m pip, чтобы pip гарантированно относился к этому интерпретатору.
Вопрос на собеседовании
«Чем модуль отличается от пакета?» — Модуль — один файл .py. Пакет — папка с модулями (обычно с __init__.py), которая сама импортируется как модуль и позволяет строить иерархию package.subpackage.module.
Итог
- Пакет — папка с
__init__.py; при импорте подпакета сначала инициализируются родители. - Абсолютные импорты — по умолчанию; относительные (
.,..) — внутри пакета, не работают в скриптах. __init__.py— место для публичного API, а не для тяжёлой логики.- src-layout +
pyproject.toml+python -m— современная структура проекта.
Практика решено 0 из 5
Файл shop/orders/service.py. Какой импорт куда ведёт?
| shop/orders/models.py | shop/payments.py | shop/orders/__init__.py | |
|---|---|---|---|
from .models import Order |
|||
from ..payments import charge |
|||
from shop.payments import charge |
|||
from . import something |
Почему при запуске python shop/orders/service.py падает ImportError: attempted relative import with no known parent package?
Что выполняется при import shop.orders?
Главное преимущество раскладки src/ (код в src/myapp/)?
Какой файл внутри пакета выполняется при команде python -m mypackage? Напишите имя файла.
Войдите, чтобы отмечать пройденные уроки.