Стандарт импортов в Python: пакет приложения внутри корня проекта, запуск через python -m или entrypoints, абсолютные импорты от имени пакета, минимум PYTHONPATH, отсутствие правок sys.path в коде. Надёжно в dev, CI и проде. И при смене CWD Всегда.
Корень проекта (<project_root>) - условная папка, где лежат код и окружение разработки/деплоя: зависимости, конфиги, скрипты, инфраструктурные файлы.Это удобный термин для людей. Для Python это не специальная сущность.
Пакет - папка с Python-модулями, обычно с __init__.py.Имя пакета появляется только тогда, когда папка пакета находится внутри одной из директорий из sys.path.
Главное правило импортов:Чтобы работало:
from app.config import SETTINGS
должно существовать:
<dir in sys.path>/
app/
__init__.py
config.py
Большинство поломок возникает из-за зависимости импортов от:
PYTHONPATH в окружении.sys.path в коде.Этот вариант является отраслевой нормой для устойчивых проектов.Если критична стабильность импортов и отсутствие сюрпризов при деплое, он не обсуждается.
/opt/example_service/ <project_root>
pyproject.toml (или requirements.txt)
.venv/
deploy/
scripts/
app/ <import_root package>
__init__.py
config.py
telemetry/
__init__.py
agent.py
clone/
__init__.py
tools/
__init__.py
helpers.py
Абсолютные импорты от имени пакета:
from app.config import SETTINGS from app.telemetry.agent import main from app.clone.tools.helpers import prepare_name
Почему именно так:
config, tools, utils больше не "болтаются" глобально);Из корня проекта:
cd /opt/example_service /opt/example_service/.venv/bin/python -m app.telemetry.agent
Почему это надёжно:
В pyproject.toml:
[project.scripts] example-agent = "app.telemetry.agent:main"
Запуск:
example-agent
Плюсы:
[program:example-agent]
directory=/opt/example_service
environment=PYTHONUNBUFFERED=1
command=/opt/example_service/.venv/bin/python -m app.telemetry.agent
Смысл:
directory фиксирует рабочую директорию;-m фиксирует пакетную семантику;PYTHONPATH не нужен как обязательное условие.service.configЭто не импорты от "корня проекта".Это импорты от пакета с именем service.
Чтобы работало:
from service.config import X
нужна структура:
<project_root>/
service/
__init__.py
config.py
То есть папка service должна быть отдельным пакетом внутри корня проекта.
__init__.py прямо в <project_root>Вариант:
/var/example_service/
__init__.py
config.py
Интуитивно кажется, что теперь пакет называется example_service. Но Python так не думает.
Чтобы импортировать "пакет, равный корню проекта", нужен родитель корня проекта в sys.path.
Например:
/var/example_servicesys.path должен быть /varВ реальности при запуске из корня:
cd /var/example_service python ...
в sys.path окажется /var/example_service, а не /var.Поэтому:
import example_service
не станет стабильно работать только из-за __init__.py в корне.
Вывод: Добавление
__init__.pyв корень проекта - не архитектурное решение.
Путь в файловой системе (/srv, /opt, /var, /home) не влияет на модель импортов.
Важна только логика:
<dir in sys.path>/
<package_name>/
Если проект лежит в:
/var/service/
service/
__init__.py
то это корректный вариант для server.*. Если лежит так:
/var/service/
__init__.py
то ожидаемого import service не будет без нестандартного sys.path.
Иногда нужно быстро и без перестройки структуры.
PYTHONPATH как эксплуатационная подпоркаПример:
/opt/example_service/
telemetry/agent.py
conf.py
Если файл из подпапки импортирует модуль из корня:
import conf
Тогда рабочий вариант окружения:
directory=<project_root>PYTHONPATH=<project_root>Это соответствует ранее обсуждённой логике: PYTHONPATH добавляет корень проекта в sys.path, и модуль из корня становится доступным.
Но:
Эти пункты почти гарантируют будущие проблемы:
sys.path в кодеimport sys sys.path.insert(0, "..")
from tools.helpers import X # при фактической структуре app/clone/tools
python app/telemetry/agent.py
вместо:
python -m app.telemetry.agent
<project_root> есть ровно один явный пакет приложения:app/
from app.... import ...
python -m app....
PYTHONPATH - не обязательное условие работоспособности.sys.path.Если цель - чтобы всё работало и не ломалось при любой смене CWD, способа запуска и окружения, применяется один устойчивый подход:
python -m или entry points,sys.path в коде,PYTHONPATH только как редкий временный компромисс.Это и есть золотой стандарт для долгоживущих Python-сервисов.
<project_root>/
.venv/
example_service/
__init__.py
config.py
telemetry/
__init__.py
agent_spool_ship.py
tasks/
__init__.py
worker.py
clone/
__init__.py
lib/
__init__.py
template/
__init__.py
Только так:
from example_service.config import ... from example_service.telemetry.agent_spool_ship import ...
cd <project_root> .venv/bin/python3 -m example_service.telemetry.agent_spool_ship .venv/bin/python3 -m example_service.tasks.worker
directory=<project_root> environment=PYTHONUNBUFFERED=1 command=<project_root>/.venv/bin/python3 -m example_service.telemetry.agent_spool_ship
<project_root>/.venv/bin/python3 -m pip install -r requirements.txt # или <project_root>/.venv/bin/python3 -m pip install -e .
example_service.* возможен только если существует папка example_service/ как пакет.sys.path должен быть родитель пакета (обычно <project_root>).python -m example_service....