Python: Настройка окружения
Теория: Точка входа
В разработке приложений есть такое понятие как точка входа. Понимание того, как правильно выделять точки входа, может значительно улучшить архитектуру вашего проекта и расширить его возможности.
Точка входа для приложения
Большинство приложений — это достаточно объемный проект, состоящий из множества модулей. Но когда запускается приложение, то запускается какой-то один конкретный исполняемый файл, он называется точкой входа:
Выше пример небольшого проекта из двух файлов:
- my_application/module.py — определена функция, выводящая сообщение в консоль
- my_application/scripts/main.py — импортируется и вызывается функция
Модуль main.py в таком проекте является точкой входа. Точку входа, как и прочие исполняемые файлы, принято располагать в директории scripts. А сами исполняемые файлы часто называют скриптами. Также принято все необходимые вызовы делать внутри main(), функции без аргументов. И уже вызывая эту функцию, мы запускаем приложение.
В примере выше мы вызываем Python из окружения проекта командой uv run. Флаг -m означает "вызвать файл как модуль". В таком случае, Python осведомлен, что вызываемый файл не одиночный скрипт, а часть проекта с импортами из других модулей. Путь до модуля нужно передавать в формате через точку, начиная от корня проекта.
Специальная переменная __name__
В проекте бывают несколько точек входа. Каждая может работать как отдельное приложение и при этом использовать общую логику. Обычно в точке входа происходит вызов одной функции. Могут быть исключения, но всегда лучше стремиться к вызову одной единственной функции.
Создадим еще один скрипт и импортируем в него предыдущий:
Запустим его:
Сообщение вывелось дважды. Почему так? Чтобы импортировать функцию из модуля, интерпретатор читает и загружает модуль полностью. В этот момент будут выполнены все определения и вызовы на уровне модуля. А значит, импортируя первый скрипт во второй, мы еще раз вызовем функцию main(). Выходит, нам нужно как-то различать ситуации двух типов:
- Модуль работает как скрипт — выполняем вызовы
- Модуль импортируется — не выполняем вызовы
Для решения этой задачи мы можем воспользоваться специальной переменной __name__. Это одна из многих специальных переменных, которые загружаются интерпретатором на старте.
Кажется, что у переменной необычное имя — в нем целых четыре символа подчеркивания. На самом деле такие имена часто встречаются в Python-коде и как правило имеют какой-то специальный смысл. Опытный разработчик обычно помнит наизусть пару десятков таких переменных, поэтому про эти переменные любят спрашивать на собеседованиях.
Посмотрим, что хранит переменная __name__ в каждом конкретном случае:
- Если происходит запуск в качестве скрипта, то переменная получает специальное значение — строку
'__main__' - Если происходит обычный импорт, то эта переменная содержит полное имя модуля
Проверив значение этой переменной, мы можем отличить запуск в качестве скрипта от импортирования. Перепишем первый скрипт main.py с применением этого нового знания:
И проверим:
Сообщение вывелось один раз, ведь условие if __name__ == "__main__": при импорте не выполнится, и функция main() не вызовется.
Изначальный скрипт тоже работает как полагается:
uv и точка входа
Каждый раз вводить полное имя модуля довольно затратно, да и неправильно. Пользователь нашей программы не должен знать о ее внутреннем устройстве, о структуре файлов. Должна быть короткая команда для запуска программы. uv предоставляет возможность указать точку входа проекта, после чего его можно запускать одной командой uv run <точка-входа>.
Для начала нужно отредактировать файл конфигурации проекта pyproject.toml:
Мы добавляем две группы секции. Первая, [build-system] и [XX.build.targets.wheel] указывает менеджеру, что наш проект это пакет со своими импортами и точкой входа. При следующем запуске, uv также установит наш проект в виртуальное окружение.
Вторая группа, [project.scripts] указывает на точки входа в проекте. Название точки записывается через дефис. А путь до нее указывается как полный путь, через точку, до скрипта и после двоеточия сама функция запуска.
Теперь мы можем запустить проект короткой командой uv run <точка-входа>:
Точка входа для библиотеки
Если в приложениях точкой входа является место, где происходит вызов самого приложения в виде функции или какого-то кода, то для библиотек ситуация иная. В библиотеках мы не должны в обычной ситуации вызывать код. Библиотека предоставляет функцию или набор функций, а когда их вызывать решает тот, кто импортирует библиотеку в свой модуль.
Например, библиотека more-itertools предоставляет различный набор вспомогательных функций. Мы можем импортировать любое количество функций из этой библиотеки, и сами решать какую когда использовать:
Когда мы указываем подобный импорт, мы не указываем путь до конкретного файла, откуда нужно импортировать функцию, а только указываем название библиотеки. Но как тогда интерпретатор узнает где находится нужная нам функция? Для решения, нам нужно экспортировать "наружу", на верхний уровень библиотеки необходимые функции или целые модули. В этом нам понадобится файл __init__.py
Обычная задача модуля __init__.py в том, чтобы указывать интерпретатору, что директория с файлами это пакет, чтобы он мог разруливать импорты. Но также в нем можно перечислить все функции, что мы хотим экспортировать наружу. Добавим в __init__.py нашего проекта запись:
Сперва мы импортируем необходимые нам функции или целые модули. Затем вписываем кортеж из них в еще одну особую переменную __all__. Теперь, тем, кто будет использовать эту библиотеку, не нужно знать в каком файле определены функции. При импорте нашей библиотеки, экспортируется все перечисленное в __all__.
Подобный файл с экспортами функций и модулей для пользования нашей библиотекой называют фасадом.
Итог
Мы познакомились с точками входа для приложений и библиотек. Разобрали, что для приложений точка входа является местом, где начинается выполнение кода, а для библиотек это модуль __init__.py с экспортом сущностей. Все, что мы обсудили, тесно связано с проектированием проекта и позволит заложить фундамент хорошей архитектуры для сложных проектов.

