- Точка входа для приложения
- Специальная переменная __name__
- uv и точка входа
- Точка входа для библиотеки
- Итог
В разработке приложений есть такое понятие как точка входа. Понимание того, как правильно выделять точки входа, может значительно улучшить архитектуру вашего проекта и расширить его возможности.
Точка входа для приложения
Большинство приложений — это достаточно объемный проект, состоящий из множества модулей. Но когда запускается приложение, то запускается какой-то один конкретный исполняемый файл, он называется точкой входа:
# file: my_application/module.py
def greet():
print('Welcome to my application!')
# file: my_application/scripts/main.py
from my_application.module import greet
def main():
greet()
main()
Выше пример небольшого проекта из двух файлов:
- my_application/module.py — определена функция, выводящая сообщение в консоль
- my_application/scripts/main.py — импортируется и вызывается функция
.
|-- pyproject.toml
|-- uv.lock
`-- my_application
|-- module.py
`-- scripts
`-- main.py
Модуль main.py в таком проекте является точкой входа. Точку входа, как и прочие исполняемые файлы, принято располагать в директории scripts. А сами исполняемые файлы часто называют скриптами. Также принято все необходимые вызовы делать внутри main()
, функции без аргументов. И уже вызывая эту функцию, мы запускаем приложение.
uv run python3 -m my_application.scripts.main
Welcome to my application!
В примере выше мы вызываем Python из окружения проекта командой uv run
. Флаг -m
означает "вызвать файл как модуль". В таком случае, Python осведомлен, что вызываемый файл не одиночный скрипт, а часть проекта с импортами из других модулей. Путь до модуля нужно передавать в формате через точку, начиная от корня проекта.
Специальная переменная __name__
В проекте бывают несколько точек входа. Каждая может работать как отдельное приложение и при этом использовать общую логику. Обычно в точке входа происходит вызов одной функции. Могут быть исключения, но всегда лучше стремиться к вызову одной единственной функции.
Создадим еще один скрипт и импортируем в него предыдущий:
# file: my_application/scripts/second_script.py
from my_application.scripts.main import main
def second_main():
main()
second_main()
Запустим его:
uv run python3 -m my_application.scripts.second_script
Welcome to my application!
Welcome to my application!
Сообщение вывелось дважды. Почему так? Чтобы импортировать функцию из модуля, интерпретатор читает и загружает модуль полностью. В этот момент будут выполнены все определения и вызовы на уровне модуля. А значит, импортируя первый скрипт во второй, мы еще раз вызовем функцию main()
. Выходит, нам нужно как-то различать ситуации двух типов:
- Модуль работает как скрипт — выполняем вызовы
- Модуль импортируется — не выполняем вызовы
Для решения этой задачи мы можем воспользоваться специальной переменной __name__
. Это одна из многих специальных переменных, которые загружаются интерпретатором на старте.
Кажется, что у переменной необычное имя — в нем целых четыре символа подчеркивания. На самом деле такие имена часто встречаются в Python-коде и как правило имеют какой-то специальный смысл. Опытный разработчик обычно помнит наизусть пару десятков таких переменных, поэтому про эти переменные любят спрашивать на собеседованиях.
Посмотрим, что хранит переменная __name__
в каждом конкретном случае:
- Если происходит запуск в качестве скрипта, то переменная получает специальное значение — строку
'__main__'
- Если происходит обычный импорт, то эта переменная содержит полное имя модуля
Проверив значение этой переменной, мы можем отличить запуск в качестве скрипта от импортирования. Перепишем первый скрипт main.py
с применением этого нового знания:
from my_application.module import greet
def main():
greet()
if __name__ == "__main__":
main()
И проверим:
uv run python3 -m my_application.scripts.second_script
Welcome to my application!
Сообщение вывелось один раз, ведь условие if __name__ == "__main__":
при импорте не выполнится, и функция main()
не вызовется.
Изначальный скрипт тоже работает как полагается:
uv run python3 -m my_application.scripts.main
Welcome to my application!
uv и точка входа
Каждый раз вводить полное имя модуля довольно затратно, да и неправильно. Пользователь нашей программы не должен знать о ее внутреннем устройстве, о структуре файлов. Должна быть короткая команда для запуска программы. uv
предоставляет возможность указать точку входа проекта, после чего его можно запускать одной командой uv run <точка-входа>
.
Для начала нужно отредактировать файл конфигурации проекта pyproject.toml:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["my_application"]
[project.scripts]
my-app = "my_application.scripts.main:main"
Мы добавляем две группы секции. Первая, [build-system]
и [XX.build.targets.wheel]
указывает менеджеру, что наш проект это пакет со своими импортами и точкой входа. При следующем запуске, uv
также установит наш проект в виртуальное окружение.
Вторая группа, [project.scripts]
указывает на точки входа в проекте. Название точки записывается через дефис. А путь до нее указывается как полный путь, через точку, до скрипта и после двоеточия сама функция запуска.
Теперь мы можем запустить проект короткой командой uv run <точка-входа>
:
uv run my-app
Welcome to my application!
Точка входа для библиотеки
Если в приложениях точкой входа является место, где происходит вызов самого приложения в виде функции или какого-то кода, то для библиотек ситуация иная. В библиотеках мы не должны в обычной ситуации вызывать код. Библиотека предоставляет функцию или набор функций, а когда их вызывать решает тот, кто импортирует библиотеку в свой модуль.
Например, библиотека more-itertools
предоставляет различный набор вспомогательных функций. Мы можем импортировать любое количество функций из этой библиотеки, и сами решать какую когда использовать:
from more_itertools import sliced, substrings
subs = ["".join(s) for s in substrings("more")]
print(subs)
slices = list(sliced((1, 2, 3, 4, 5, 6, 7, 8), 3))
print(slices)
Когда мы указываем подобный импорт, мы не указываем путь до конкретного файла, откуда нужно импортировать функцию, а только указываем название библиотеки. Но как тогда интерпретатор узнает где находится нужная нам функция? Для решения, нам нужно экспортировать "наружу", на верхний уровень библиотеки необходимые функции или целые модули. В этом нам понадобится файл __init__.py
Обычная задача модуля __init__.py в том, чтобы указывать интерпретатору, что директория с файлами это пакет, чтобы он мог разруливать импорты. Но также в нем можно перечислить все функции, что мы хотим экспортировать наружу. Добавим в __init__.py нашего проекта запись:
# мы также можем импортировать под другим именем через as
import my_application.module as my_lib
from my_application.module import greet
__all__ = (
"my_lib",
"greet",
)
Сперва мы импортируем необходимые нам функции или целые модули. Затем вписываем кортеж из них в еще одну особую переменную __all__
. Теперь, тем, кто будет использовать эту библиотеку, не нужно знать в каком файле определены функции. При импорте нашей библиотеки, экспортируется все перечисленное в __all__
.
from my_application import my_lib, greet
my_lib.greet()
# => Welcome to my application!
greet()
# => Welcome to my application!
Подобный файл с экспортами функций и модулей для пользования нашей библиотекой называют фасадом.
Итог
Мы познакомились с точками входа для приложений и библиотек. Разобрали, что для приложений точка входа является местом, где начинается выполнение кода, а для библиотек это модуль __init__.py с экспортом сущностей. Все, что мы обсудили, тесно связано с проектированием проекта и позволит заложить фундамент хорошей архитектуры для сложных проектов.
Самостоятельная работа
- В проекте python-package изучите точку запуска
- Попробуйте запустить проект с указанием полного пути к точке запуска
- Сделайте фасад библиотеки
- Добавьте все изменения на гитхаб
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.