1 added
1 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>В разработке приложений есть такое понятие как точка входа. Понимание того, как правильно выделять точки входа, может значительно улучшить архитектуру вашего проекта и расширить его возможности.</p>
1
<p>В разработке приложений есть такое понятие как точка входа. Понимание того, как правильно выделять точки входа, может значительно улучшить архитектуру вашего проекта и расширить его возможности.</p>
2
<h2>Точка входа для приложения</h2>
2
<h2>Точка входа для приложения</h2>
3
<p>Большинство приложений - это достаточно объемный проект, состоящий из множества модулей. Но когда запускается приложение, то запускается какой-то один конкретный исполняемый файл, он называется<em>точкой входа</em>:</p>
3
<p>Большинство приложений - это достаточно объемный проект, состоящий из множества модулей. Но когда запускается приложение, то запускается какой-то один конкретный исполняемый файл, он называется<em>точкой входа</em>:</p>
4
<p>Выше пример небольшого проекта из двух файлов:</p>
4
<p>Выше пример небольшого проекта из двух файлов:</p>
5
<ul><li><em>my_application/module.py</em>- определена функция, выводящая сообщение в консоль</li>
5
<ul><li><em>my_application/module.py</em>- определена функция, выводящая сообщение в консоль</li>
6
<li><em>my_application/scripts/main.py</em>- импортируется и вызывается функция</li>
6
<li><em>my_application/scripts/main.py</em>- импортируется и вызывается функция</li>
7
</ul><p>Модуль<em>main.py</em>в таком проекте является точкой входа. Точку входа, как и прочие исполняемые файлы, принято располагать в директории<em>scripts</em>. А сами исполняемые файлы часто называют<em>скриптами</em>. Также принято все необходимые вызовы делать внутри main(), функции без аргументов. И уже вызывая эту функцию, мы запускаем приложение.</p>
7
</ul><p>Модуль<em>main.py</em>в таком проекте является точкой входа. Точку входа, как и прочие исполняемые файлы, принято располагать в директории<em>scripts</em>. А сами исполняемые файлы часто называют<em>скриптами</em>. Также принято все необходимые вызовы делать внутри main(), функции без аргументов. И уже вызывая эту функцию, мы запускаем приложение.</p>
8
<p>В примере выше мы вызываем Python из окружения проекта командой uv run. Флаг -m означает "вызвать файл как модуль". В таком случае, Python осведомлен, что вызываемый файл не одиночный скрипт, а часть проекта с импортами из других модулей. Путь до модуля нужно передавать в формате через точку, начиная от корня проекта.</p>
8
<p>В примере выше мы вызываем Python из окружения проекта командой uv run. Флаг -m означает "вызвать файл как модуль". В таком случае, Python осведомлен, что вызываемый файл не одиночный скрипт, а часть проекта с импортами из других модулей. Путь до модуля нужно передавать в формате через точку, начиная от корня проекта.</p>
9
<h2>Специальная переменная __name__</h2>
9
<h2>Специальная переменная __name__</h2>
10
<p>В проекте бывают несколько точек входа. Каждая может работать как отдельное приложение и при этом использовать общую логику. Обычно в точке входа происходит вызов одной функции. Могут быть исключения, но всегда лучше стремиться к вызову одной единственной функции.</p>
10
<p>В проекте бывают несколько точек входа. Каждая может работать как отдельное приложение и при этом использовать общую логику. Обычно в точке входа происходит вызов одной функции. Могут быть исключения, но всегда лучше стремиться к вызову одной единственной функции.</p>
11
<p>Создадим еще один скрипт и импортируем в него предыдущий:</p>
11
<p>Создадим еще один скрипт и импортируем в него предыдущий:</p>
12
<p>Запустим его:</p>
12
<p>Запустим его:</p>
13
<p>Сообщение вывелось дважды. Почему так? Чтобы импортировать функцию из модуля, интерпретатор читает и загружает модуль полностью. В этот момент будут выполнены все определения и<strong>вызовы</strong>на уровне модуля. А значит, импортируя первый скрипт во второй, мы еще раз вызовем функцию main(). Выходит, нам нужно как-то различать ситуации двух типов:</p>
13
<p>Сообщение вывелось дважды. Почему так? Чтобы импортировать функцию из модуля, интерпретатор читает и загружает модуль полностью. В этот момент будут выполнены все определения и<strong>вызовы</strong>на уровне модуля. А значит, импортируя первый скрипт во второй, мы еще раз вызовем функцию main(). Выходит, нам нужно как-то различать ситуации двух типов:</p>
14
<ul><li>Модуль работает как скрипт - выполняем вызовы</li>
14
<ul><li>Модуль работает как скрипт - выполняем вызовы</li>
15
-
<li>Модуль импортируется - не выполняем вызовы</li>
15
+
<li>Модуль импортируетс�� - не выполняем вызовы</li>
16
</ul><p>Для решения этой задачи мы можем воспользоваться специальной переменной __name__. Это одна из многих специальных переменных, которые загружаются интерпретатором на старте.</p>
16
</ul><p>Для решения этой задачи мы можем воспользоваться специальной переменной __name__. Это одна из многих специальных переменных, которые загружаются интерпретатором на старте.</p>
17
<p>Кажется, что у переменной необычное имя - в нем целых четыре символа подчеркивания. На самом деле такие имена часто встречаются в Python-коде и как правило имеют какой-то специальный смысл. Опытный разработчик обычно помнит наизусть пару десятков таких переменных, поэтому про эти переменные любят спрашивать на собеседованиях.</p>
17
<p>Кажется, что у переменной необычное имя - в нем целых четыре символа подчеркивания. На самом деле такие имена часто встречаются в Python-коде и как правило имеют какой-то специальный смысл. Опытный разработчик обычно помнит наизусть пару десятков таких переменных, поэтому про эти переменные любят спрашивать на собеседованиях.</p>
18
<p>Посмотрим, что хранит переменная __name__ в каждом конкретном случае:</p>
18
<p>Посмотрим, что хранит переменная __name__ в каждом конкретном случае:</p>
19
<ul><li>Если происходит запуск в качестве скрипта, то переменная получает специальное значение - строку '__main__'</li>
19
<ul><li>Если происходит запуск в качестве скрипта, то переменная получает специальное значение - строку '__main__'</li>
20
<li>Если происходит обычный импорт, то эта переменная содержит полное имя модуля</li>
20
<li>Если происходит обычный импорт, то эта переменная содержит полное имя модуля</li>
21
</ul><p>Проверив значение этой переменной, мы можем отличить запуск в качестве скрипта от импортирования. Перепишем первый скрипт main.py с применением этого нового знания:</p>
21
</ul><p>Проверив значение этой переменной, мы можем отличить запуск в качестве скрипта от импортирования. Перепишем первый скрипт main.py с применением этого нового знания:</p>
22
<p>И проверим:</p>
22
<p>И проверим:</p>
23
<p>Сообщение вывелось один раз, ведь условие if __name__ == "__main__": при импорте не выполнится, и функция main() не вызовется.</p>
23
<p>Сообщение вывелось один раз, ведь условие if __name__ == "__main__": при импорте не выполнится, и функция main() не вызовется.</p>
24
<p>Изначальный скрипт тоже работает как полагается:</p>
24
<p>Изначальный скрипт тоже работает как полагается:</p>
25
<h2>uv и точка входа</h2>
25
<h2>uv и точка входа</h2>
26
<p>Каждый раз вводить полное имя модуля довольно затратно, да и неправильно. Пользователь нашей программы не должен знать о ее внутреннем устройстве, о структуре файлов. Должна быть короткая команда для запуска программы. uv предоставляет возможность указать точку входа проекта, после чего его можно запускать одной командой uv run <точка-входа>.</p>
26
<p>Каждый раз вводить полное имя модуля довольно затратно, да и неправильно. Пользователь нашей программы не должен знать о ее внутреннем устройстве, о структуре файлов. Должна быть короткая команда для запуска программы. uv предоставляет возможность указать точку входа проекта, после чего его можно запускать одной командой uv run <точка-входа>.</p>
27
<p>Для начала нужно отредактировать файл конфигурации проекта<em>pyproject.toml</em>:</p>
27
<p>Для начала нужно отредактировать файл конфигурации проекта<em>pyproject.toml</em>:</p>
28
<p>Мы добавляем две группы секции. Первая, [build-system] и [XX.build.targets.wheel] указывает менеджеру, что наш проект это<strong>пакет</strong>со своими импортами и точкой входа. При следующем запуске, uv также установит наш проект в виртуальное окружение.</p>
28
<p>Мы добавляем две группы секции. Первая, [build-system] и [XX.build.targets.wheel] указывает менеджеру, что наш проект это<strong>пакет</strong>со своими импортами и точкой входа. При следующем запуске, uv также установит наш проект в виртуальное окружение.</p>
29
<p>Вторая группа, [project.scripts] указывает на точки входа в проекте. Название точки записывается через дефис. А путь до нее указывается как полный путь, через точку, до скрипта и после двоеточия сама функция запуска.</p>
29
<p>Вторая группа, [project.scripts] указывает на точки входа в проекте. Название точки записывается через дефис. А путь до нее указывается как полный путь, через точку, до скрипта и после двоеточия сама функция запуска.</p>
30
<p>Теперь мы можем запустить проект короткой командой uv run <точка-входа>:</p>
30
<p>Теперь мы можем запустить проект короткой командой uv run <точка-входа>:</p>
31
<h2>Точка входа для библиотеки</h2>
31
<h2>Точка входа для библиотеки</h2>
32
<p>Если в приложениях точкой входа является место, где происходит вызов самого приложения в виде функции или какого-то кода, то для библиотек ситуация иная. В библиотеках мы не должны в обычной ситуации вызывать код. Библиотека предоставляет функцию или набор функций, а когда их вызывать решает тот, кто импортирует библиотеку в свой модуль.</p>
32
<p>Если в приложениях точкой входа является место, где происходит вызов самого приложения в виде функции или какого-то кода, то для библиотек ситуация иная. В библиотеках мы не должны в обычной ситуации вызывать код. Библиотека предоставляет функцию или набор функций, а когда их вызывать решает тот, кто импортирует библиотеку в свой модуль.</p>
33
<p>Например, библиотека more-itertools предоставляет различный набор вспомогательных функций. Мы можем импортировать любое количество функций из этой библиотеки, и сами решать какую когда использовать:</p>
33
<p>Например, библиотека more-itertools предоставляет различный набор вспомогательных функций. Мы можем импортировать любое количество функций из этой библиотеки, и сами решать какую когда использовать:</p>
34
<p>Когда мы указываем подобный импорт, мы не указываем путь до конкретного файла, откуда нужно импортировать функцию, а только указываем название библиотеки. Но как тогда интерпретатор узнает где находится нужная нам функция? Для решения, нам нужно экспортировать "наружу", на верхний уровень библиотеки необходимые функции или целые модули. В этом нам понадобится файл<em>__init__.py</em></p>
34
<p>Когда мы указываем подобный импорт, мы не указываем путь до конкретного файла, откуда нужно импортировать функцию, а только указываем название библиотеки. Но как тогда интерпретатор узнает где находится нужная нам функция? Для решения, нам нужно экспортировать "наружу", на верхний уровень библиотеки необходимые функции или целые модули. В этом нам понадобится файл<em>__init__.py</em></p>
35
<p>Обычная задача модуля<em>__init__.py</em>в том, чтобы указывать интерпретатору, что директория с файлами это пакет, чтобы он мог разруливать импорты. Но также в нем можно перечислить все функции, что мы хотим экспортировать наружу. Добавим в<em>__init__.py</em>нашего проекта запись:</p>
35
<p>Обычная задача модуля<em>__init__.py</em>в том, чтобы указывать интерпретатору, что директория с файлами это пакет, чтобы он мог разруливать импорты. Но также в нем можно перечислить все функции, что мы хотим экспортировать наружу. Добавим в<em>__init__.py</em>нашего проекта запись:</p>
36
<p>Сперва мы импортируем необходимые нам функции или целые модули. Затем вписываем кортеж из них в еще одну особую переменную __all__. Теперь, тем, кто будет использовать эту библиотеку, не нужно знать в каком файле определены функции. При импорте нашей библиотеки, экспортируется все перечисленное в __all__.</p>
36
<p>Сперва мы импортируем необходимые нам функции или целые модули. Затем вписываем кортеж из них в еще одну особую переменную __all__. Теперь, тем, кто будет использовать эту библиотеку, не нужно знать в каком файле определены функции. При импорте нашей библиотеки, экспортируется все перечисленное в __all__.</p>
37
<p>Подобный файл с экспортами функций и модулей для пользования нашей библиотекой называют<strong>фасадом</strong>.</p>
37
<p>Подобный файл с экспортами функций и модулей для пользования нашей библиотекой называют<strong>фасадом</strong>.</p>
38
<h2>Итог</h2>
38
<h2>Итог</h2>
39
<p>Мы познакомились с точками входа для приложений и библиотек. Разобрали, что для приложений точка входа является местом, где начинается выполнение кода, а для библиотек это модуль<em>__init__.py</em>с экспортом сущностей. Все, что мы обсудили, тесно связано с проектированием проекта и позволит заложить фундамент хорошей архитектуры для сложных проектов.</p>
39
<p>Мы познакомились с точками входа для приложений и библиотек. Разобрали, что для приложений точка входа является местом, где начинается выполнение кода, а для библиотек это модуль<em>__init__.py</em>с экспортом сущностей. Все, что мы обсудили, тесно связано с проектированием проекта и позволит заложить фундамент хорошей архитектуры для сложных проектов.</p>