Прежде чем писать на Python код, который кто-то будет запускать кроме вас, нужно разобраться с тем, как этот код вообще запускается и где живут его зависимости. Эта статья — про рабочую среду: интерпретатор, виртуальные окружения, установку библиотек, описание проекта и автоматическое форматирование.
Зачем вообще нужны инструменты
Python — интерпретируемый язык: исходный код не компилируется в отдельный исполняемый файл заранее, а читается и выполняется специальной программой — интерпретатором. Когда вы пишете python script.py, операционная система запускает интерпретатор, а он уже построчно исполняет ваш файл.
Из этого вытекает главная проблема новичка: код не существует «сам по себе». Он зависит от того, какой именно интерпретатор его запустил и какие библиотеки этому интерпретатору доступны. Два проекта на одной машине легко могут требовать разные версии одной и той же библиотеки — и без инструментов изоляции они начнут конфликтовать. Всё, о чём ниже, решает ровно эту задачу: сделать запуск предсказуемым и воспроизводимым.
Интерпретатор и версии
«Python» как программа — это и есть интерпретатор. У него есть версия, и она важна: синтаксис и стандартная библиотека меняются от версии к версии. Актуальная линейка на момент статьи — Python 3.14; 3.12 и 3.13 ещё поддерживаются, и почти все библиотеки работают на них.
Проверить, какая версия установлена:
python --version # например: Python 3.14.0
python3 --version # на macOS/Linux часто нужен именно python3
Важная тонкость: в системе обычно живёт несколько интерпретаторов одновременно. Системный Python (которым пользуется сама ОС), Python, который вы поставили сами, версии из разных проектов. Команда python указывает на какой-то один из них — на какой именно, зависит от настроек окружения. Поэтому первое правило: никогда не ставьте библиотеки в системный Python — можно сломать инструменты операционной системы. Для каждого проекта заводят отдельное изолированное окружение.
Если на машине нужно держать несколько версий самого интерпретатора (например, 3.11 для одного проекта и 3.12 для другого), для этого есть отдельные менеджеры версий (pyenv и подобные) — но для начала достаточно одной свежей версии.
Виртуальные окружения (venv)
Виртуальное окружение — это отдельная папка с собственной копией интерпретатора и собственным набором установленных библиотек. Проекты в разных окружениях не видят библиотеки друг друга, поэтому их зависимости не конфликтуют.
У каждого проекта своя копия интерпретатора-ссылки и свои пакеты; две версии одной библиотеки на одной машине не мешают друг другу.
Короткая формула: одно окружение — один проект.
Создаётся окружение встроенным модулем venv:
# создаём окружение в папке .venv внутри проекта
python -m venv .venv
# активируем его (macOS/Linux)
source .venv/bin/activate
# активируем его (Windows)
.venv\Scripts\activate
После активации в начале строки терминала появляется (.venv), а команда python начинает указывать на интерпретатор из этого окружения. Всё, что вы теперь установите, попадёт в .venv, а не в систему. Выйти из окружения — командой deactivate.
Папку .venv не кладут в систему контроля версий (её добавляют в .gitignore): окружение легко пересоздать из описания зависимостей, а весит оно много.
pip и зависимости
pip — это менеджер пакетов Python. Он скачивает библиотеки из центрального репозитория PyPI (Python Package Index) и устанавливает их в активное окружение.
pip install requests # поставить библиотеку
pip install "requests==2.31.0" # конкретную версию
pip uninstall requests # удалить
pip list # что установлено в окружении
Чтобы проект мог собрать кто-то другой (или вы на другой машине), список зависимостей нужно зафиксировать. Классический способ — файл requirements.txt:
# выгрузить текущие зависимости в файл
pip freeze > requirements.txt
# установить всё из файла в новое окружение
pip install -r requirements.txt
Так воспроизводится среда: создал окружение → поставил зависимости из файла → код работает так же, как у автора. Сегодня вместо «голого» requirements.txt всё чаще описывают зависимости в pyproject.toml — о нём дальше.
pyproject.toml и структура пакета
pyproject.toml — это стандартный файл описания Python-проекта. Один файл в формате TOML, в котором собрано всё: имя и версия проекта, его зависимости, а также настройки инструментов (того же форматтера и линтера). Он пришёл на смену разрозненным setup.py, setup.cfg и отдельным конфигам.
Минимальный пример:
[project]
name = "my-service"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"requests>=2.31",
"fastapi>=0.110",
]
[tool.ruff]
line-length = 100
Секция [project] описывает сам проект и его зависимости (dependencies). Секции вида [tool.<имя>] — это настройки конкретных инструментов; они живут в том же файле, поэтому конфигурация проекта не растекается по десятку файлов.
Типичная структура пакета для backend-проекта выглядит так:
my-service/
├── pyproject.toml # описание проекта и зависимостей
├── README.md
├── src/
│ └── my_service/ # сам пакет (папка с __init__.py)
│ ├── __init__.py
│ └── main.py
└── tests/
└── test_main.py
Пакет — это просто папка с файлом __init__.py (он может быть пустым); такая папка становится единицей импорта. Раскладка с папкой src/ — общепринятая: она не даёт случайно импортировать код «из текущей директории» вместо установленного пакета и делает тесты честнее.
Форматтеры и линтеры (ruff, black)
Два разных инструмента, которые часто путают:
- Форматтер автоматически приводит код к единому виду — расставляет отступы, кавычки, переносы строк. Он не меняет смысл кода, только внешний вид. Самый известный — black.
- Линтер ищет проблемы: неиспользованные импорты, подозрительные конструкции, нарушения стиля. Он не переписывает код (хотя многое умеет чинить), а указывает на ошибки. Сегодня стандарт — ruff: он очень быстрый и заменяет сразу множество старых инструментов, а с недавних пор умеет и форматировать.
ruff check . # проверить код на проблемы
ruff check --fix . # проверить и починить то, что можно
ruff format . # отформатировать (аналог black)
black . # отформатировать через black
Зачем это нужно: единый стиль убирает споры о форматировании в команде и делает изменения в истории проекта чистыми (в правках виден смысл, а не перестановка кавычек). Настройки задаются в pyproject.toml — в секциях [tool.ruff] и [tool.black].
Запуск скриптов и модулей
Запустить файл напрямую — самый очевидный способ:
python script.py # выполнить файл script.py
Но у Python есть второй, более «правильный» для пакетов способ — запуск модуля через флаг -m:
python -m my_service.main # запустить модуль внутри пакета
python -m venv .venv # так же запускается сам venv
python -m pip install ... # и pip — это тоже модуль
Разница принципиальна. При python script.py интерпретатор считает «корнем» папку самого файла. При python -m имя.модуля корнем считается текущая директория, и Python ищет модуль как часть пакета — поэтому внутренние импорты пакета работают корректно. Для одиночных скриптов годится первый способ, для кода внутри пакета — почти всегда -m.
Отдельно стоит знать про конструкцию:
live example
def main() -> None:
print("привет")
if __name__ == "__main__": # выполнится только при прямом запуске файла
main()
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
Переменная __name__ равна "__main__", когда файл запущен напрямую, и равна имени модуля, когда файл импортирован из другого кода. Этот приём позволяет одному и тому же файлу быть и запускаемым скриптом, и импортируемым модулем без побочных эффектов при импорте.
Где Python ищет модули: sys.path
Когда вы пишете import my_service.orders, интерпретатор перебирает список путей и берёт первый, где нашёлся пакет. Этот список называется sys.path, и половина ошибок ModuleNotFoundError объясняется тем, что в нём нет нужной папки:
live example
import sys
for path in sys.path:
print(path)
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
Первым элементом стоит каталог скрипта при python script.py или текущий каталог при python -m пакет.модуль, дальше стандартная библиотека и site-packages активного окружения, куда ставит пакеты pip. Поэтому python src/my_service/main.py ломает импорты внутри пакета: корнем становится src/my_service, а не src, и import my_service.orders не находится. Лечат это установкой проекта в окружение в режиме разработки (pip install -e .), после чего пакет виден отовсюду, или запуском через -m из корня. Переменная окружения PYTHONPATH добавляет каталоги в начало списка, и это аварийный инструмент для скриптов, а не способ собрать сервис.
Глубже: uv, lock-файлы и pipxрасширенное
Всё, что выше, можно делать одной программой. uv объединяет виртуальные окружения, установку
пакетов, выбор версии самого Python и фиксацию зависимостей, и делает это на порядок быстрее pip:
uv init my-service # pyproject.toml и каркас проекта
uv add fastapi # записать зависимость и поставить её
uv sync # собрать окружение ровно по uv.lock
uv run python -m my_service.main
Файл uv.lock — это и есть то, чем pip freeze был в мире requirements: точные версии всех
пакетов, включая транзитивные, одинаковые у всех разработчиков и в контейнере. Разница в том,
что dependencies в pyproject.toml описывают намерение («любой FastAPI не ниже 0.110»), а lock-файл — факт («в этот раз поставили 0.137.2»). Без lock-файла сборка через полгода
может подтянуть другие версии и сломаться на ровном месте.
Инструменты командной строки вроде ruff или httpie ставят не в окружение проекта, а
отдельно: pipx install ruff или uv tool install ruff — тогда у них своё изолированное
окружение, и их версии не спорят с зависимостями проекта. И последнее: когда непонятно,
какой pip сработал, пишите python -m pip — это гарантированно pip того интерпретатора,
который вы назвали.
Коротко
- Python интерпретируемый: код выполняет интерпретатор, и важно, какой именно и с какими библиотеками. Актуальная версия — 3.14.
- Виртуальное окружение (
python -m venv .venv+ активация) изолирует зависимости проекта; правило — одно окружение на проект, в систему библиотеки не ставим. - pip ставит пакеты из PyPI; список зависимостей фиксируют, чтобы среду можно было воспроизвести.
pyproject.toml— единый файл описания проекта: имя, версия, зависимости и настройки инструментов.- Пакет — папка с
__init__.py; типичная раскладка —src/<пакет>/иtests/. - ruff (линтер + форматтер) и black (форматтер) держат код в едином стиле; настройки — в
pyproject.toml. - Запуск:
python script.pyдля одиночных файлов,python -m пакет.модульдля кода внутри пакета; блокif __name__ == "__main__":делает файл и скриптом, и модулем. uvобъединяет venv, pip, версии Python и lock-файл;uv.lockфиксирует факт,pyproject.toml— намерение; инструменты вроде ruff ставят черезpipxилиuv tool.
Что почитать дальше
- Синтаксис и типы данных — базовый синтаксис, переменные и встроенные типы.
- Аннотации типов (type hints) — как добавлять типы и зачем они нужны в backend-коде.
- Функции и модули — как организовать код в функции, модули и пакеты.