Как структурировать проект на Python: лучшие практики

Раздел: Разработка на Python -> Управление проектами

Основные подходы к управлению проектами на Python

Современный подход с Poetry

Poetry объединяет управление зависимостями, виртуальным окружением и сборкой пакета. Он автоматически создаёт и синхронизирует файл poetry.lock, что гарантирует воспроизводимость окружения.

Как создать новый проект и добавить зависимости?

poetry new my_project
cd my_project
poetry add requests

проект программа на python (проект программы на python)

Команда poetry new создаёт стандартную структуру:

my_project/
├── pyproject.toml
├── README.rst
├── my_project/
│   └── __init__.py
└── tests/
    ├── __init__.py
    └── test_my_project.py

Для установки зависимостей используется poetry add, для разработки - poetry add --dev pytest. Виртуальное окружение создаётся автоматически при первом запуске.

Типичные проблемы

  • Версия Poetry может не соответствовать версии Python в системе - используйте pipx для изолированной установки Poetry.
  • Конфликты зависимостей: Poetry уведомляет о несовместимости версий, требует разрешения вручную.
  • При работе с закрытым PyPI-репозиторием требуется настройка [[tool.poetry.source]].

Цели использования

Подходит для проектов любой сложности, особенно тех, которые в дальнейшем будут опубликованы как пакеты. Обеспечивает чистую изоляцию и простой CI/CD.

Вариант 1: Классический способ - pip + virtualenv

Как создать изолированное окружение и установить зависимости?

python -m venv venv
source venv/bin/activate  # или venv\Scripts\activate для Windows
pip install -r requirements.txt

Зависимости фиксируются вручную в requirements.txt, файл блокировок (lock) отсутствует. При обновлении зависимостей разработчик сам решает, какие версии зафиксировать.

Ошибки и их решение

  • Случайное использование глобального pip - всегда активируйте виртуальное окружение.
  • Различия версий Python между средами - используйте pyenv для управления версиями.
  • Забыли добавить зависимость в requirements.txt - проверяйте список через pip freeze.

Когда применять

Для небольших скриптов, прототипов или проектов, где не требуется воспроизводимость на уровне lock-файла.

Вариант 2: Pipenv - автоматизация окружения

Как автоматизировать создание окружения и управление зависимостями?

pipenv install requests
pipenv shell

Pipenv создаёт файлы Pipfile и Pipfile.lock, автоматически активирует окружение при входе в каталог через pipenv shell.

Характерные сложности

  • Медленная работа при большом количестве зависимостей.
  • Иногда возникают конфликты с уже установленными пакетами в глобальной среде.
  • Прекращение активной поддержки Pipenv многими сообществами.

Область применения

Подходит для простых веб-проектов или утилит, где важна простота создания окружения.

Вариант 3: Conda - управление не только Python

Как управлять не только Python, но и системными библиотеками (C/C++, R)?

conda create -n myenv python=3.9 numpy
conda activate myenv

Conda устанавливает пакеты из каналов conda-forge и defaults, разрешая зависимости на уровне бинарных файлов.

Проблемы при использовании

  • Большой размер дистрибутива (Miniconda ~500 МБ, Anaconda ~3 ГБ).
  • Медленное разрешение зависимостей из‑за поиска по всем каналам.
  • Лицензионные ограничения при коммерческом использовании некоторых пакетов.

Сценарии применения

Незаменим для Data Science, машинного обучения, где требуются пакеты с нативным кодом (TensorFlow, CUDA).

Расширенный пример структуры проекта с Poetry

Создадим проект с поддержкой тестирования, линтинга и pre-commit хуков.

Пример
poetry new advanced_project
cd advanced_project
poetry add click requests
poetry add --dev pytest pytest-cov black flake8 mypy pre-commit
poetry install

Файл pyproject.toml после добавления зависимостей:

Пример
[tool.poetry]
name = "advanced-project"
version = "0.1.0"
description = ""
authors = ["Your Name "]

[tool.poetry.dependencies]
python = "^3.8"
click = "*"
requests = "*"

[tool.poetry.dev-dependencies]
pytest = "*"
pytest-cov = "*"
black = "*"
flake8 = "*"
mypy = "*"
pre-commit = "*"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Настроим pre-commit. Создадим файл .pre-commit-config.yaml:

Пример
repos:
  - repo: https://github.com/psf/black
    rev: 23.7.0
    hooks:
      - id: black
  - repo: https://github.com/pycqa/flake8
    rev: 6.1.0
    hooks:
      - id: flake8
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.4.1
    hooks:
      - id: mypy
Пример
poetry run pre-commit install  # устанавливаем хуки в .git/hooks

Результат работы pre-commit при коммите:

black....................................................................Passed
flake8...................................................................Passed
mypy.....................................................................Passed

Интеграция с GitHub Actions

Файл .github/workflows/ci.yml для автоматического запуска тестов:

Пример
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11"]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Poetry
        run: curl -sSL https://install.python-poetry.org | python3 -
      - name: Install dependencies
        run: poetry install
      - name: Run tests
        run: poetry run pytest --cov

Результат в логах CI:

collected 10 items
tests/test_advanced_project.py .........                          [100%]

----------- coverage: platform linux, python 3.11.0 -----------
Name                                 Stmts   Miss  Cover
--------------------------------------------------------
advanced_project/__init__.py             2      0   100%
advanced_project/main.py                15      1    93%
tests/test_advanced_project.py          20      0   100%
--------------------------------------------------------
TOTAL                                  37      1    97%

Тестирование нескольких версий Python с помощью tox

Добавим в pyproject.toml секцию [tool.tox]:

Пример
[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py39, py310, py311

[testenv]
deps = pytest
commands = pytest
"""

Запуск tox:

Пример
poetry run tox

Вывод:

py39 run-test: commands[0] | pytest
============================= test session starts =============================
collected 10 items
tests/test_advanced_project.py .........                         [100%]

============================== 10 passed in 0.15s =============================
py310 run-test: commands[0] | pytest
...
py311 run-test: commands[0] | pytest
...
___________________________________ summary ___________________________________
  py39: commands succeeded
  py310: commands succeeded
  py311: commands succeeded
  congratulations :)

Параметризованные тесты с pytest

Пример тестирования функции сложения:

Пример
# advanced_project/calculations.py
def add(a, b):
    return a + b

# tests/test_calculations.py
import pytest
from advanced_project.calculations import add

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
    (2.5, 3.1, 5.6),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

Результат:

collected 4 items
tests/test_calculations.py ....                                 [100%]

Проект программы на Python - comments

En
проект программа на python (python)