Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Поддержать проект
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Структура проекта и импорты без боли
project_structure_and_imports

Структура проекта и импорты без боли

Раскладываем код и тесты так, чтобы pytest находил всё без ModuleNotFoundError. Понимаем flat vs src layout, настраиваем editable install

⚠️ Урок устарел. Свежая версия в курсе pytest-basics: Базовая структура: где хранить тесты.

#Цель урока

✅ Научиться организовывать код, когда модулей больше одного
✅ Запускать тесты без ModuleNotFoundError одной командой
✅ Настроить IDE один раз и забыть про проблемы с импортами

#Шаг 1. Зачем вообще нужна структура?

Пока был один файл — всё работало. Но в реальности появляется второй модуль.

bank-app/
  src/
    bank_account.py      # основной класс
    transaction_log.py   # логирование операций ← новый модуль
    email_sender.py      # уведомления ← ещё один
  tests/
    test_bank_account.py

src/transaction_log.py:

class TransactionLog: def __init__(self): self.transactions = [] def log(self, message): self.transactions.append(message)

src/bank_account.py:

from .transaction_log import TransactionLog class BankAccount: def __init__(self): self.balance = 0 self.transaction_log = TransactionLog() def deposit(self, amount): self.balance += amount self.transaction_log.log(f"Deposited {amount}")

Без понятной структуры модули не смогли бы импортировать друг друга.

#Шаг 2. Минимальная рабочая структура

Создайте базовый скелет:

bank-app/
  src/
    bank_account.py
  tests/
    test_bank_account.py

tests/test_bank_account.py:

from src.bank_account import BankAccount # импорт через src! def test_deposit(): account = BankAccount() account.deposit(100) assert account.balance == 100

Запуск ВСЕГДА из корня проекта:

# PYTHONPATH=. говорит Python: «ищи модули в текущей папке» # Без этого: Python не видит src/ как пакет # С этим: работает import from src.bank_account ... PYTHONPATH=. pytest -v

#Шаг 3. Настройка IDE (выберите свою)

VS Code:

# В корне проекта создайте .env echo "PYTHONPATH=." > .env # Перезапустите VS Code — готово

PyCharm:

1) Правый клик на корне проекта
2) Mark Directory as → Sources Root
3) Готово — импорты работают

#Шаг 4. Алиас для удобства

Добавьте в ~/.zshrc или ~/.bashrc:

alias pt='PYTHONPATH=. pytest -v'

Теперь вместо длинной команды — просто pt.

#Проверка себя

❌ Неправильно: запускать pytest из tests/
✅ Правильно: из корня проекта (pwd показывает папку с src/ и tests/)

❌ Неправильно: from bank_account import BankAccount
✅ Правильно: from src.bank_account import BankAccount

#Шаг 5. Src layout — профессиональный стандарт

Проблема flat layout: тесты могут случайно импортировать локальные модули вместо установленного пакета. При установке через pip install . пакет может вести себя иначе, чем локально.

Решение: Src layout изолирует исходный код в отдельной папке src/.

#Flat layout (простой, для обучения)

my_project/
  my_package/
    __init__.py
    app.py
  tests/
    test_app.py
  pyproject.toml

Плюсы: Просто, быстро начать Минусы: Тесты могут импортировать неустановленный код

#Src layout (профессиональный, для production)

my_project/
  src/
    my_package/
      __init__.py
      app.py
  tests/
    test_app.py
  pyproject.toml

Плюсы:

  • Тесты работают с установленным кодом (как это будет в production)
  • Изоляция: невозможно импортировать неустановленный код
  • Стандарт в Python-сообществе (рекомендация PyPA)

Минусы: Требует pip install -e . для разработки

#Пример: переход на src layout

До (flat):

bank-app/
  bank_account.py
  tests/
    test_bank_account.py

После (src):

bank-app/
  src/
    bank_app/
      __init__.py
      bank_account.py
  tests/
    test_bank_account.py
  pyproject.toml

Тесты меняются:

# До from bank_account import BankAccount # После from bank_app.bank_account import BankAccount

#Шаг 6. Editable install — работа как профи

Проблема: При src layout нужно устанавливать пакет после каждого изменения.

Решение: Editable install (pip install -e .) — один раз установили, код обновляется автоматически.

#Создайте pyproject.toml

# pyproject.toml в корне проекта [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "bank-app" version = "0.1.0" dependencies = [] [project.optional-dependencies] dev = [ "pytest>=7.0", "pytest-cov>=4.0", ]

#Установите в режиме разработки

# Editable install (один раз) pip install -e . # Установите dev-зависимости pip install -e ".[dev]" # Теперь pytest работает без PYTHONPATH pytest -v

Что произошло:

  • Пакет установлен в окружение, но указывает на локальные файлы
  • Изменения в коде сразу видны без переустановки
  • Импорты работают везде (терминал, IDE, pytest)

#Сравнение подходов

ПодходКоманда запускаПлюсыМинусы
PYTHONPATHPYTHONPATH=. pytestБыстро начатьНестабильно, не работает везде
Editable installpytest (один раз настроил)Профессионально, стабильноНужен pyproject.toml

#Шаг 7. Практика: настройте свой проект

Задача: Создайте проект с src layout и editable install.

# 1. Создайте структуру mkdir -p my_todo/src/todo my_todo/tests cd my_todo # 2. Создайте __init__.py touch src/todo/__init__.py # 3. Создайте код cat > src/todo/app.py << 'EOF' class TodoList: def __init__(self): self.tasks = [] def add(self, task): self.tasks.append(task) return len(self.tasks) EOF # 4. Создайте тест cat > tests/test_app.py << 'EOF' from todo.app import TodoList def test_add_task(): todo = TodoList() count = todo.add("Learn pytest") assert count == 1 assert todo.tasks == ["Learn pytest"] EOF # 5. Создайте pyproject.toml cat > pyproject.toml << 'EOF' [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "todo" version = "0.1.0" [project.optional-dependencies] dev = ["pytest>=7.0"] EOF # 6. Установите в editable режиме pip install -e ".[dev]" # 7. Запустите тесты (без PYTHONPATH!) pytest -v

Результат: Тесты проходят, импорты работают, код готов к production.

#Проверка себя

✅ Понимаю разницу между flat и src layout ✅ Создал pyproject.toml для своего проекта ✅ Установил пакет через pip install -e . ✅ Тесты запускаются без PYTHONPATH ✅ Импорты работают в терминале, IDE и pytest

❌ Неправильно: запускать pytest из tests/ ✅ Правильно: из корня проекта (pwd показывает папку с src/ и tests/)

❌ Неправильно: PYTHONPATH=. pytest для каждого запуска ✅ Правильно: один раз pip install -e ., потом просто pytest

#Частые вопросы

Вопрос 1: Обязательно ли использовать src layout для обучения? Ответ: Нет. Flat layout проще для начала. Но для реальных проектов лучше сразу src.

Вопрос 2: Что делать, если pip install -e . не работает? Ответ: Проверьте:

  • pyproject.toml в корне проекта
  • Правильный build-backend (setuptools)
  • Виртуальное окружение активировано

Вопрос 3: Нужно ли переустанавливать после изменения кода? Ответ: Нет! Editable install (-e) автоматически видит изменения.

Далее: Тестируем классы и ошибки: pytest.raises