Comments 62
зачем такое при использовании как пакета?
опять же есть __init__
за уши удалось примерную стратегию применения притянуть, но несколько сомнительна
Допустим, вы хотите этот модуль не только импортировать, но и при случае использовать из командной строки как-нибудь так:
python -m image_utils -convert=png someimage.jpg
Так команда не сработает, даже если положить if __name__ == '__main__' внутрь __init__.py. Напишет:
No module named image_utils.__main__; 'image_utils' is a package and cannot be directly executed
Как быть? Кладёте внутрь папки __main__.py, куда добавляете обработку командной строки и всё начинает работать. Бонусом, что не надо больше постоянно писать if __name__ == '__main__'
init.py делает папку модулем, который можно импортировать для других проектов.
Начиная с Python 3.3 __init__.py
для этого не нужен.
Допустим, вы хотите этот модуль не только импортировать, но и при случае использовать из командной строки как-нибудь так:
Нужно каждый раз писать python -m
, неудобно же. Удобнее и проще регистрировать скрипты.
Я думаю, так мало людей использует __main__.py
как раз из-за того, что setup.py
представляет более удобный функционал.
python -m http.server
и python -m compileall .
?python -m venv /path/to/venv
туда же. Очень удобно, имхо.Альтернативный вариант — использовать исполняемый файл pyvenv.
Удобнее это тем, что можно создавать venv с явно указанной версией и инсталляцией python, например `/opt/bin/python3.8 -m venv`. Кроме того, бинарь pyvenv с какой-то версии объявлен депрекейтед.
Использовал, удобно.
Никогда не использовали python -m http.server и python -m compileall .?
Использовал, конечно. А вы никогда не использовали jupyter notebook
? По-моему, удобнее, чем python -m jupyter notebook
.
без всякого этого -m и python
Ответьте на вопрос — а нужно ли это ???
Если очень хочется, ну, или сделайте симлинк на этот main.py из /usr/local/bin, или положите туда тупой двухстрочный шелл-скрипт, который за Вас сделает python -m module-name @$
а шелл-скрипт — замечательное в своей оптимальности решение.
Ага, больше врапперов богу врапперов.
А Вы не подумали, что просто положив скрипт на питоне куда-либо — Вы убиваете всю возможность его версионировать. И доставлять через родное для питона окружение (pip install). Все равно по уму тогда нужно либо начать писать свои пакеты нативные для операционной системы, т.к. упаковывать python скрипты в deb, rpm — это вот все. И правильно прописывать зависимости от других пакетов (поверьте, это сложно). Либо использовать какие-то внешние способы доставки (а-ля ansible).
Касательно примеров вызова python -m против сразу вызов скрипта — смотрите ansible, сам pip, j2 из j2cli и пр. консольные утилиты, написанные на Питоне. Можете даже запилить сравнение, что они делают, чтобы быть доступными для пользователя по "простой" команде. И, да, в половине случаев, если не больше — Вы увидите враппер (может не на Шелл, но на питоне).
а зачем тут вобще врапперы?
просто нужный файлик делаем исполняемым.
инерпретатор указываем env python (env python3)
и все замечательно запускается, версионифецируется
Нет. Неверно. Тот же pip — он устанавливается в систему по определенному пути (/usr/bin/pip), что не оставляет возможности иметь ДВА pip'а в системе. В случае с python3 выкрутились попросту устанавливая его в /usr/bin/pip3, но все равно при определенных обстоятельствах основной указывает не туда, куда надо. Более того — то, что по этому пути это не полноценный pip, а враппер:
#!/usr/bin/python3
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.1.1','console_scripts','pip3.7'
__requires__ = 'pip==19.1.1'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pip==19.1.1', 'console_scripts', 'pip3.7')()
)
о чем выше я и сообщил.
При использовании его через python3 -m pip — есть гарантия, что он всегда вызывается в нужном окружении.
И, да, в половине случаев, если не больше — Вы увидите враппер (может не на Шелл, но на питоне).
Вы ведь в курсе, что никто врапперы для питоновских модулей вручную не пишет, а они генерируются автоматически по записи в entry_points
?
Прикол в том, что эти врапперы могут отнимать целых полсекунды на запуск из-за импорта модуля pkg_resources, поэтому я иногда пишу врапперы вручную на шелле, потому что они тупо быстрее)
Гораздо удобнее прописать в setup.py
все точки входа. И тогда не нужно запускать интерпретатор с ключом -m
, так как все точки входа будут обычными консольными командами. if __name__ == '__main__'
в этом случае тоже не нужен.
Ага, больше логики в setup.py.
Я лично считаю упомянутую Вами практику порочной. Не вижу бенефитов от точек входа в setup.py, кроме путаницы, если нужно установить один модуль в два и более интерпретатора.
О какой логике речь? Мне кажется, вы не совсем поняли, что я имею в виду. Нужно лишь дописать в setup.py
что-то вроде:
setuptools.setup(
...
entry_points={
'console_scripts': [
'my_script=my_module:main',
],
},
...
И после установки через pip
у вас появится общесистемная команда my_script
. И точка входа main может находиться где угодно в соответствии с логикой кода, не обязательно в __main__.py
.
Если нужно установить один модуль в несколько интерпретаторов, то это значит, что у вас несколько виртуальных окружений. Соответственно, какое окружение активно, такой скрипт и будет вызван.
И это не порочная практика, а повсеместная и рекомендованная Python Packaging Authority. Посмотрите в bin/
своего виртуального окружения. И взгляните на официальный пример проекта от PyPA.
Этот файл придётся исключать из coverage при тестировании, ведь импорт модуля __main__.py
приведёт к запуску программы. Это приведёт к тому, что в __main__.py
будет очень простой код, например код ниже, чтобы этот код не требовал покрытия тестами. В итоге, мы вернёмся к if __name__ == '__main__'
import run
run.main()
Видел очень много проектов, в которых в __init__.py
написано очень много логики.
И что? Миллион мух не может ошибаться? То что все говнячат — это, во-первых, не означает, что это нормально, а, во-вторых, отвечайте сами за свой продукт.
В третьих, наличие всякой хитрой логики, что в setup.py, что в init.py — за это надо руки отрывать
импорт модуля main.py приведёт к запуску программы
Нет, если в __main__.py
точно так же запускать программу внутри if __name__ == '__main__'
если называние модуля __main__.py
, то ваша проверка лишена смысла: модуль и так будет всегда называться __main__
(по названию файла), как бы вы его не запускали или импортировали.
Попробуйте
python my_package/__main__.py
) или через -m (python -m my_package
). Если __main__.py
импортируется из другого модуля, его __name__
будет my_package.__main__
.Считаю что оформлять приложения в виде модулей это плохая идея, вместо этого использую *.py
в корне проекта для приложений и подкаталоги для библиотек. Так сразу видно что можно запускать напрямую без поиска __main__.py
, не нужно никаких python -m
для запуска, не создаётся искусственных связей между приложениями и библиотеками, которые потом мешают расширять проект (т.е. добавлять новые приложения, разбивать и объединять библиотеки, разбивать весь репозиторий). Примерная структура одного из текущих проектов:
├── mycoolservice-updater.py # backend демон обновляющий данные
├── mycoolservice-*.py # какие-то ещё вспомогательные backend утилиты
├── mycoolservice-webapp.py # веб-приложение
├── mycoolservice # основная логика, используется всеми
│ ├── __init__.py
│ └── *.py
└── mycoolserviceweb # логика специфичная для веб приложения, вьюхи
├── __init__.py
└── *.py
А __main__.py
я бы оставил только для особой функциональности модулей типа python -m json.tool
И потом получать геморрой с публикацией всех этих скриптов на PyPI?
Никакого геморроя. Много кто так и делает, см., например, ansible и black.
Ок, я забыл про эту фичу (но всё равно не вижу смысла так делать)
setup(
# ...
scripts=[
'bin/ansible',
'bin/ansible-playbook',
'bin/ansible-pull',
'bin/ansible-doc',
'bin/ansible-galaxy',
'bin/ansible-console',
'bin/ansible-connection',
'bin/ansible-vault',
'bin/ansible-config',
'bin/ansible-inventory',
],
# ...
)
Оформлять приложения в виде модулей это наиболее правильная идея, которая может возникнуть в рамках инфраструктуры и экосистемы питона. Типичный пример. Мне часто приходится работать с несколькими версиями питона на одной машине. И речь не про 2 vs 3, а про версии даже внутри одной линейки. Если скрипт запускать как исполняемый файл, то велика вероятность, что он запуститься не тем интерпретатором. В случае вызова python3.6 -m имя_модуля такой двусмысленности нет. И более того — если интерпретатор вырубается на отсутствующий модуль, то будет понятно, что делать.
Скрипт можно запускать точно так же, причём на 1 аргумент короче.
Сомнительное преимущество. Здесь скорее дело в том, как "скрипт" установлен — как отдельно стоящий скрипт в /usr/bin и аналогах или как полноценный питон модуль.
В любом случае вопрос ещё в том, как грамотно обернуть питон-модуль — или в pypi, или в нативный пакет для операционной системы. Меня, честно говоря, очень расстраивает, когда setup.py приносит в систему то, что не ожидается изначально. Примеров масса.
Если ставить пакеты в виртуальные окружения, то всё, что пакеты приносят, остаётся в директории виртуального окружения и проблем нет. Всегда можно снести и поставить пакеты заново, если что-то пошло не так.
Ставить как нативные для системы особого смысла нет. Лишняя возня со сборкой, а премуществ никаких особо и нет.
Ну, чего все такие категоричные? Я вообще ума приложить не могу.
Часть вещей — действительно имеет смысл ставить в venv. Даже тот же хваленый airflow. Ansible — тоже скорее да, чем нет. Пользоваться, конечно, становится неудобно, но для сервисов это не проблема — обернул в сервис и понеслась. Часть вещей — не имеет смысла ставить в venv. Вообще — это какие-то общесистемные вещи, например, пакет для управления docker. Или еще что-то подобное. Ну, и опять же — мой тезис, что setup.py в теории может любую дрянь притащить в систему. Мне кажется, что не нужно пытаться противопоставлять средства нативной доставки приложений (deb/rpm в первую очередь) и pip, а все-таки пытаться их дружить. Благо в первых все-таки есть средства рекурсивной валидации зависимостей — для Питон-экосистемы это прям больное место.
Да, может, с категоричностью я палку перегнул, наверное. Просто сужу с точки зрения задач, которые именно я решал. Спорить не буду, я уже ниже написал, что каждый инструмент хорош, когда его используют по назначению.
Насчёт дряни — это к мейнтейнеру. Скрипты deb-пакетов тоже могут дел натворить. Или вы плавно ведёте к экзотике вроде Nix/Guix?
Ставить как нативные для системы особого смысла нет.
Не будь смысла, опакечивание Python модулей не было бы распространено повсеместно: https://repology.org/projects/?search=python%3A
Лишняя возня со сборкой, а премуществ никаких особо и нет.
Возня со сборкой получается когда пакеты собираются вне системного репозитория, и зачастую не могут найти нужные нативные библиотеки и компилятор. А потом получается ещё больше возни, потому что системные библиотеки обновляются или удаляются, не зная ничего о том что от них зависят питоновские модули, в итоге ломая последние. Нет, система управления пакетами должна быть единой для всего.
Не будь смысла, опакечивание Python модулей не было бы распространено повсеместно:
Я, возможно, потерял нить разговора, но речь шла о собственных проектах. Не берусь судить о всех, но лично мне проще устанавливать пакеты через pip
прямо из репозитория git
, чем поддерживать пакеты и держать репозитории для разных операционных систем. Чтоб не компилировать каждый раз бинарные модули, можно держать один репозиторий с wheel-файлами. Это удобнее, если, например, разработка ведётся под MacOS, на серверах Ubuntu, а в докере вообще какой-нибудь Alpine.
Возня со сборкой получается когда пакеты собираются вне системного репозитория, и зачастую не могут найти нужные нативные библиотеки и компилятор.
Допускаю, что такое возможно, хотя я с таким не сталкивался. Ещё раз замечу, что дискуссия ведётся в контексте организации своего проекта, так как статья была об этом. А не о преимуществах и недостатках разных способов дистрибуции вообще.
У меня же сложилось такое мнение (не обязательно верное). Если пакет зависит от каких-то нативных библиотек, то ничто не мешает проверять наличие этих библиотек при компиляции и установке. Или вовсе помещать их в wheel-файл. Пакеты под линукс следует собирать не на коленке, а в официальном докере manylinux1, что даст какую-то уверенность в работоспособности. Мне кажется, что перечисленные проблемы — это не проблема способа дистрибуции, а проблема мейнтейнера. Вот, тот же numpy. Отлично ставится через pip
, хотя насквозь бинарный.
С другой стороны, если пользоваться установленными в систему пакетами, то как работать с виртуальными окружениями? Да, на сервере это нечасто нужно, чтоб стояло несколько окружений. Но на машине разработчика — очень даже.
В общем, всё средства хороши на своём месте.
Мы тоже pip'ом ставим из гита. Это действительно удобно и способствует быстрой разработке.
Но нормальные пакеты (в первую очередь deb/rpm, во вторую pypi/wheel) становятся жизненно необходимы, если этими модулями будут пользоваться другие люди. Условно — решил проблему — поделись с сообществом. Ничего там сверхсекретного в твоей разработке скорее всего нет. Тот же ML — там ноу-хау в моделях (т.е. весовых коэффициентах), а не во фреймворках обучения.
Касательно дружить — да, все верно, что setup py ничего не знает о системе, куда будет установлен. Скажем, Вам нужна библиотека для работы с БД Postgres. В разных дистрибутивах она может быть разной версии, лежать по разным путям и устанавливаться из пакетов с разными именами. Унификации здесь нет и в ближайшее время не предвидится. В результате ставя питон-пакет из официального репозитория ОС — эти зависимости подтягиваются, т.к. мейнтейнеры их описали и гарантируют их. А ставя через pip — либо как повезет, либо там будет дичь в setup.py.
Насчёт дичи в deb/rpm — да, она тоже возможна, но мейнтейнеры официальных репозиториев такое не пропустят, т.е. проблема возможна только со сторонними репами
или в pypi, или в нативный пакет для операционной системы
Я ни разу не встречал проблем с опакечиванием питоновых приложений хоть с PyPI, хоть напрямую с GitHub — setup.py
делает всё что нужно и создаёт исполняемые файлы в bin
— это работает и c __main.py__
, и со скриптами.
Проблема, повторюсь, возникает при работе с репозиторием, потому что с __main__.py
скриптов в нём не будет и чтобы пользоваться приложением из чекаута репозитория вам придётся, по сути, сначала руками распарсить entry_points
из setup.py
, или искать __main__.py
.
`python -m venv`
`json.tool` — выше написали :)
А вот если это действительно приложение, да еще и не одно вращается на сервере или более того в контейнерах, то использование __main__.py или просто run.py даже вредит. Когда приходит OOM Killer, мы не видим какое конкретно приложение было «убито», а только python. Стараемся использовать шебанг в service_name.py или иное указываем в README.md.
Очень спорный аргумент
В случае оом — лучше настроить эти питоновские скрипты как юниты системди и указать им политику рестарта...
[1395278.160214] python invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=996
Для одного-двух проектов это легко отыскать и исправить проблему. Если же больше, то иди-разберись какое из 30+ приложений упало. А если это еще приложение в kubernetes/swarm со скейлом 3 (мой вариант), то совсем не радостно становится искать логи упавшего сервиса. А еще надо как то в тикете описать что нужно править :)
Вы несомненно правы, что искать по логам ООМкиллера не очень удобно. Но это очень похоже на борьбу не с причиной проблемы, а со следствием. Смотрите.
- В случае системд юнитов, почему я вообще о них заговорил, часть головной боли по задаче "понять, что отвалилось" перекладывается на системд. Ес-но, это не полное решение. Рестарт — это такая же затычка и Вам решать нужно ли его ставить (как будто в кубе приложения автоматически не перезапускаются в случае сбоя).
- Обвесить все мониторингом. Вообще все. Любое долгоживущие приложение должно быть под мониторингом. Упало? Получили Алерт и это увидели. С пакетными заданиями сложнее, но тоже реально. В этом кейсе становится сложнее понять, если отвалился внутренний компонент приложения, а не его основная часть, но тоже нужно смотреть по ситуации — наверняка есть решение.
- В идеальном мире — оом быть не должно. Разработчики должны ставить сайзинг на все свои решения. С небольшим, но запасом. И в лучшем случае ООМ никогда не будет, но ценой наличия незадействованных ОЗУ.
2. Не получится. На некоторых приложениях настроен kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale.
3. Запас есть, но он тоже ограничен.
Кубер при скейлинге так же может упереться в ограничение по количеству подов на рабочей ноде (110 штук, такое у нас пока не возможно) или в лимит ОЗУ прописанный в деплоях. Период опроса мониторинга 15 секунд и когда словишь «эффект домино», то несколько сервисов могут остановится в этом промежутке и на дашборде не будет видно в какой последовательности это произошло. Единственное место это логи ОС, но там раньше было по 2-4 записи подряд на подобии указанной мной в предыдущем комменте. Т.е. какой процесс был прибит ООМ, а какой был остановлен автоскейлером приходилось искать по тоннам логов потому, что stack trace в syslog указывал на python, а не конкретное приложение.
С одной стороны, понимаю Вашу боль, с другой — повторюсь, что в одиночку эту проблему не решить, нужно кооперироваться с разработчиками, повышать культура кодирования.
- Не получится. На некоторых приложениях настроен kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale.
В условиях ограниченных ресурсов. Ну, а что — молодцы! К сожалению, не вижу Вашей инфры, поэтому не могу дать аргументированное мнение, но выглядит прям странно.
Период опроса мониторинга 15 секунд и когда словишь «эффект домино», то несколько сервисов могут остановится в этом промежутке и на дашборде не будет видно в какой последовательности это произошло
последовательность нужна для пост-мортема, а не для починить "прямо сейчас". Очень странная у Вас конфигурация, прям скажу, потому что кубер ведь должен мочь перезапускать поды. А если даже это не так — у Вас же мониторинг на прометеусе наверняка есть? Эластик есть, куда приложения логи отправляют?
Т.е. какой процесс был прибит ООМ, а какой был остановлен автоскейлером приходилось искать по тоннам логов потому, что stack trace в syslog указывал на python, а не конкретное приложение.
ну, и чем лог syslog'а поможет в случае, если отвалился не python процесс с pid=1, а какой-то дочерний? Вы просто сделали несколько слоев абстракций (k8s -> docker -> linux) и пытаетесь как будто использовать не тот инструмент исследования не для той задачи.
последовательность нужна для пост-мортема, а не для починить «прямо сейчас». Очень странная у Вас конфигурация, прям скажу, потому что кубер ведь должен мочь перезапускать поды.
пост-мортем используется для локализации проблемы со стороны разработчиков то же. Вроде я не писал про невозможность перезапуска подов? Кубер с этим очень хорошо справляется.
Я писал про цепочку падения микросервисов. Они восстанавливаются сразу же, но это не есть хорошо. По этому пишу пост-мортем, делаю тикет, туда всю инфу пишу и уже разработка разрабатывает :) Так что в одиночку я ничего не устраняю. У меня на это гораздо больше времени уйдет, чем у человека который писал этот микросервис.
ну, и чем лог syslog'а поможет в случае, если отвалился не python процесс с pid=1, а какой-то дочерний?
Jun 17 09:33:06 slave-1 kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
Jun 17 09:33:06 slave-1 kernel: [25793] 0 25793 394 62 4 0 0 http_server
Jun 17 09:33:06 slave-1 kernel: [25851] 0 25851 1911 1488 7 0 0 python
Jun 17 09:33:06 slave-1 kernel: Memory cgroup out of memory: Kill process 25851 (python) score 1459 or sacrifice child
видно какой сервис отвалился. Не просто python, а http_server
Стараемся использовать шебанг в service_name.py или иное указываем в README.md.
Просто зарегистрируйте точку входа в setup.py
и скрипт запуска с шебангом будет создан автоматически. А для Windows и вовсе exe-файл, если вдруг вам Windows нужен.
Официальная документация рекомендует использовать setuptools
для модулей, а те — регистрировать точки входа в setup.py
. Инфы не мало, просто она неочевидным образом распределена. Если только начинаете разбираться, то посмотрите на официальный пример модуля. Там много комментариев, и охвачены разные аспекты: от пакетирования до тестирования.
__main__.py
имеет смысл использовать, если мы не делаем полноценный модуль, который будем устанавливать, а просто сделали модуль в виде папочки или zip-архива и так и запускаем.
chmod +x ./myapp/__main__.py
./myapp/__main__.py
Это плохой подход. Вот причины:
- нужно знать путь к модулю (а мало ли в какое окружение он будет установлен),
- нужно прописать shebang,
- это не работает в Windows, например.
Регистрация точек входа в setup.py
и __main__.py
лишены этих недостатков.
Мне вот стало интересно. Условно у меня есть приложение на Flask'e, я в свою очередь решил не создавать в папке app
файл __main__.py
, а настроить .env
и запускать через flask run
.
Что мне делать в таком случае? Лично я еще не нашел решения, как прописать, что именно фласк должен запускать
Используйте __main__.py