Зарегистрируйтесь, чтобы продолжить обучение

Точка входа Python: Настройка окружения

В разработке приложений есть такое понятие как точка входа. Понимание того, как правильно выделять точки входа, может значительно улучшить архитектуру вашего проекта и расширить его возможности.

Точка входа для приложения

Большинство приложений — это достаточно объемный проект, состоящий из множества модулей. Но когда запускается приложение, то запускается какой-то один конкретный исполняемый файл, он называется точкой входа:

# 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 с экспортом сущностей. Все, что мы обсудили, тесно связано с проектированием проекта и позволит заложить фундамент хорошей архитектуры для сложных проектов.


Самостоятельная работа

  1. В проекте python-package изучите точку запуска
  2. Попробуйте запустить проект с указанием полного пути к точке запуска
  3. Сделайте фасад библиотеки
  4. Добавьте все изменения на гитхаб

Дополнительные материалы

  1. Совершенный код: проектирование функций

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 19 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»