Структура проекта и модули

Структура проекта и модули

В предыдущем уроке мы спроектировали телефонную книгу и решили разбить её на несколько файлов. Теперь разберём, как Python работает с несколькими файлами в одном проекте, и создадим скелет нашего приложения.

Несколько файлов — несколько модулей

Каждый .py-файл в Python — это модуль. Когда проект состоит из нескольких файлов, они импортируют друг друга точно так же, как импортируют стандартную библиотеку:

# contact.py — определяет классы Contact и ContactNotFoundError

# address_book.py — импортирует из contact.py:
from contact import Contact, ContactNotFoundError

# cli.py — импортирует из обоих:
from address_book import AddressBook
from contact import Contact

Python ищет модули в:

  1. Текущей директории (откуда запущен скрипт)
  2. Директориях из sys.path
  3. Стандартной библиотеке

Поэтому все файлы проекта должны лежать в одной директории — тогда они находят друг друга.

Точка входа: if name == "main"

В каждом файле Python устанавливает переменную __name__. Когда файл запускают напрямую (python main.py), __name__ равен "__main__". Когда импортируют из другого файла — __name__ равен имени файла (без .py).

Конструкция if __name__ == "__main__": позволяет разделить код, который должен выполниться только при прямом запуске, от кода, который может быть импортирован:

# main.py
from cli import run_cli

def main():
    run_cli()

if __name__ == "__main__":
    main()

Без этой защиты main() вызывался бы при любом импорте main.py — а мы хотим, чтобы он запускался только когда пользователь явно запустит python main.py.

Скелет файлов проекта

Создадим заготовки всех файлов, чтобы понять структуру:

# contact.py
class ContactNotFoundError(Exception):
    """Контакт не найден в книге."""
    pass

class Contact:
    def __init__(self, name, phone, email=None):
        self.name = name
        self.phone = phone
        self.email = email

    def __str__(self):
        parts = [f"{self.name}: {self.phone}"]
        if self.email:
            parts.append(self.email)
        return ", ".join(parts)

    def __repr__(self):
        return f"Contact({self.name!r}, {self.phone!r}, email={self.email!r})"
# address_book.py
from contact import Contact, ContactNotFoundError

class AddressBook:
    def __init__(self, filepath):
        self.filepath = filepath
        self._contacts = []
        self._load()

    def add(self, contact):
        self._contacts.append(contact)

    def find(self, name):
        # реализация в следующем уроке
        pass

    def delete(self, name):
        # реализация в следующем уроке
        pass

    def all(self):
        return list(self._contacts)

    def _load(self):
        pass  # загрузка из JSON — следующий урок

    def save(self):
        pass  # сохранение в JSON — следующий урок
# main.py
from address_book import AddressBook

CONTACTS_FILE = "contacts.json"

def main():
    book = AddressBook(CONTACTS_FILE)
    print(f"Загружено контактов: {len(book.all())}")

if __name__ == "__main__":
    main()

Зачем разделять

Разделение по файлам — это разделение ответственности:

  • contact.py — только описание данных контакта
  • address_book.py — только логика работы с коллекцией
  • cli.py — только взаимодействие с пользователем
  • main.py — только запуск

Если нужно изменить формат хранения (заменить JSON на SQLite), меняем только address_book.py. Если нужен другой интерфейс (веб вместо консоли), меняем только cli.py. Остальной код не трогаем.

Проверь себя

Когда выполнится main() в следующем файле?

# app.py
def main():
    print("запуск")

if __name__ == "__main__":
    main()

Только при прямом запуске python app.py. При импорте (from app import main) — не выполнится.

Итог

  • Каждый .py-файл — отдельный модуль; импортировать из него можно через from имя import что_нужно.
  • if __name__ == "__main__": — защита кода запуска от случайного выполнения при импорте.
  • Разделение по файлам = разделение ответственности; каждый файл знает о своей задаче.
  • Все файлы проекта должны быть в одной директории (или корректно настроен sys.path).

В следующем уроке реализуем классы Contact и AddressBook полностью.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике