У меня есть сторонний скрипт консоли Python, исходный код которого я не хочу изменять.
Но я хочу настроить протоколирование, которое выполняется скриптом и его библиотеками. Скрипт использует стандартную запись в Python, но не поддерживает его настройку.
Как я могу настроить ведение журнала, если я не хочу изменять источники консольного скрипта?
Я хочу практиковать "разделение интересов". Я хочу иметь возможность настроить ведение журнала один раз и в одном месте. Эта конфигурация должна использоваться всем кодом Python Virtualenv. Написание обертки было бы обходным решением. Я хочу решение.
Несколько месяцев спустя я думаю, что pth файл будет простым ответом.
Ответ 5
ТЛ; др
Короче говоря, мы хотим внедрить код, который выполняется интерпретатором python до того, как будет выполнен наш основной код.
Лучший способ добиться этого - создать virtualenv и добавить sitecustomize.py
в пакеты сайта virtualenv.
демонстрация
Предположим, что приложение, которое мы хотим запустить, называется my_app.py
и его регистратор имеет то же имя.
$ cat my_app.py
import logging
logger = logging.getLogger("my_app")
logger.debug("A debug message")
logger.info("An info message")
logger.warning("A warning message")
logger.error("An error message")
Запуск my_app.py
должен показывать только те сообщения, уровень серьезности которых> WARNING
(это поведение по умолчанию при ведении журнала python).
$ python my_app.py
A warning message
An error message
Теперь давайте создадим виртуальную среду
python3 -m venv my_venv
И давайте добавим sitecustomize.py
к пакетам сайта virtualenv.
$ cat my_venv/lib/python3.7/site-packages/sitecustomize.py
import logging
# Setup logging for my_app
# We will only setup a console handler
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setFormatter(
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
)
logger.addHandler(ch)
Теперь давайте попробуем запустить my_app.py
используя virtualenv:
$ ./my_venv/bin/python my_app.py
2019-01-25 16:03:16,815 - my_app - DEBUG - A debug message
2019-01-25 16:03:16,815 - my_app - INFO - An info message
2019-01-25 16:03:16,815 - my_app - WARNING - A warning message
2019-01-25 16:03:16,815 - my_app - ERROR - An error message
И это было все) Мы получили правильную регистрацию без изменения my_app.py
или написания оболочки!
Теперь, если вы хотите знать, почему это оптимальный подход, продолжайте читать.
(Действительно) длинный ответ
Прежде чем понять, почему использование virtualenv + sitecustomize.py
является правильным подходом к этой проблеме, необходимо сделать не очень краткое введение.
Примечание. Я предполагаю, что вы создаете виртуальные среды с venv
модуля venv
который использует stdlib site.py
Библиотека virtuaelnv
использует свой собственный site.py
и может делать что-то немного иначе. Тем не менее, прочитав этот ответ, вы сможете проверить, есть ли какие-либо различия между venv
и vitualenv
и понять, как с ними справиться.
Что такое site-packages
Короткий ответ: site-packages
- это место, где python устанавливает сторонний код (как в не -s коде tdlib). Для получения дополнительной информации прочитайте это и предоставленные ссылки.
Как ввести код?
Python позволяет вам настроить интерпретатор python во время его запуска, то есть до того, как будет выполнен наш основной код/скрипт/что угодно. Это может быть полезно, например:
- кода покрытия
- профайлеры
- и вообще вводить код, как нам нужно сделать для этого вопроса.
Способ внедрения - это создание/изменение либо sitecustomize.py
либо usercustomize.py
. Вы также можете использовать файл "путь конфигурации" (т.е. *.pth
) с оператором импорта, но я не буду здесь рассматривать этот случай, так как:
- Эти заявления об импорте кажутся ужасным взломом
- Я не уверен, что он дает какие-либо реальные преимущества по сравнению с
sitecustomize
/usercustomize
. - Я хотел бы сделать вещи простыми
В любом случае, если вам нужна дополнительная информация WRT к файлам конфигурации пути, вы можете проверить PyMOTW и, если вам нужен пример использования их с оператором импорта, проверить этот пост в блоге.
sitecustomize
& usercustomize
Итак, sitecustomize
и usercustomize
- это специальные файлы, которые по умолчанию не существуют, но если мы их создадим, python автоматически импортирует их, прежде чем начнет выполнять наш код. Мы можем создать эти файлы:
- либо в глобальных пакетах сайта (например,
/usr/lib/python3.7/site-packages/
) - или на сайте пользователя-пакета (например,
~/.local/lib/python3.7/site-packages/
)
sitecustomize
всегда импортируется перед usercustomize
. Если ImportError
либо из файлов отсутствует, ImportError
игнорируется.
В качестве меры безопасности, если существует несоответствие между идентификатором пользователя или группы и эффективным идентификатором, то пользовательские пакеты сайта отключаются (источник). Более того, интерпретатор python имеет аргументы CLI, которые либо полностью отключают пакеты сайта (как системные, так и пользовательские) или отключают пакеты сайта пользователя. Если предположить, что у нас нет несовпадения идентификаторов и что мы не используем какие-либо флаги CLI, то пользовательские пакеты сайтов имеют более высокий приоритет, чем системные пакеты сайтов. Так что, если у нас есть оба:
~/.local/lib/python3.7/site-packages/sitecustomize.py
/usr/lib/python3.7/site-packages/sitecustomize.py
первый - тот, который будет импортирован. На самом деле мы можем проверить приоритет sys.path, выполнив модуль site.py
:
$ python3 -msite
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/home/username/.local/lib/python3.7/site-packages', # user site-packages
'/usr/lib/python3.7/site-packages', # system site-packages
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True
Важной информацией здесь является значение ENABLE_USER_SITE
. Если это True
то пользовательские пакеты сайта включены. Если это False
то мы можем использовать только глобальные пакеты сайта. Например, если мы используем python -s
:
$ python3 -s -msite
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False
Обратите внимание, что в этом случае ENABLE_USER_SITE
имеет значение False
.
Просто для полноты позвольте полностью отключить пакеты сайта:
$ python3 -S -msite
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: None
эксперимент
Чтобы лучше это понять, давайте проведем эксперимент. Сначала давайте создадим модули usercustomize
sitecustomize
как в системных, так и в пользовательских пакетах сайта.
ВНИМАНИЕ: Мы будем создавать файлы в системных пакетах сайта. Это будет мешать вашему дистрибутиву Python. Будьте осторожны и ПОМНИТЕ, чтобы удалить их, когда мы закончим.
# system site packages
echo 'print(f"-> {__file__}")' | sudo tee /usr/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | sudo tee /usr/lib/python3.7/site-packages/sitecustomize.py
# user site packages
echo 'print(f"-> {__file__}")' | tee ~/.local/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ~/.local/lib/python3.7/site-packages/sitecustomize.py
Позвольте также создать модуль Python:
echo 'print("Inside foo")' | tee foo.py
Теперь давайте foo.py
:
$ python3 foo.py
-> /home/username/.local/lib/python3.7/site-packages/sitecustomize.py
-> /home/username/.local/lib/python3.7/site-packages/usercustomize.py
Inside foo
Как мы можем видеть:
-
sitecustomize
как sitecustomize
и usercustomize
- они импортируются из пользовательских пакетов сайта
Что будет, если мы отключим пользовательские пакеты сайтов?
$ python3 -s foo.py
-> /usr/lib/python3.7/site-packages/sitecustomize.py
Inside foo
На этот раз мы видим, что мы:
- только
sitecustomize
импортируется. Хотя usercustomize
существует в системных пакетах сайта, python не импортирует его! Это важно! Имейте это в виду, когда мы обсуждаем virtualenvs! (СОВЕТ: это связано с ENABLE_USER_SITE
; вы помните, какое значение имеет в этом случае?) -
sitecustomize
импортируется из системных пакетов сайта
Наконец, если мы полностью usercustomize
пакеты сайтов, очевидно, что usercustomize
и sitecustomize
будут игнорироваться:
$ python3 -S foo.py
Inside foo
Как насчет virtualenvs?
Хорошо, теперь давайте добавим в игру virtualenv. Есть два типа virtualenvs:
- нормальные
- созданные с помощью
--system-site-packages
.
Пусть создадут virtualenvs обоих типов
python3 -mvenv venv_no_system
python3 -mvenv venv_system
Также sitecustomize.py
usercustomize.py
модули sitecustomize.py
и usercustomize.py
в site-пакеты virtualenv:
echo 'print(f"-> {__file__}")' | tee ./venv_no_system/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_no_system/lib/python3.7/site-packages/sitecustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_system/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_system/lib/python3.7/site-packages/sitecustomize.py
и давайте посмотрим на различия:
$ ./venv_no_system/bin/python -msite
/tmp/test/venv_no_system/lib/python3.7/site-packages/sitecustomize.py
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/tmp/test/venv_no_system/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False
Что мы видим здесь? На обычном virtualenv ENABLE_USER_SITE
имеет значение False
, что означает, что:
- пакеты сайта пользователя игнорируются
- только
sitecustomize
импортируется! Т.е. мы не можем использовать usercustomize
для внедрения кода !!!
Мы также видим, что вместо наших глобальных пакетов сайтов, virtualenv использует свой собственный (т.е. /tmp/test/venv_no_system/lib/python3.7/site-packages
).
Теперь давайте повторим это, но на этот раз с virtualenv, который использует системные пакеты сайта:
$ ./venv_system/bin/python -msite
-> /home/username/.local/lib/python3.7/site-packages/sitecustomize.py
-> /home/username/.local/lib/python3.7/site-packages/usercustomize.py
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/tmp/test/venv_system/lib/python3.7/site-packages',
'/home/username/.local/lib/python3.7/site-packages',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True
В этом случае поведение отличается...
ENABLE_USER_SITE
- это True
что означает:
- что пользовательские пакеты сайта включены
- что
usercustomize
импортируется нормально.
Но есть и еще одно отличие. В этом случае у нас есть 3 каталоги сайтов-пакетов. Virtualenv - это пакет с более высоким приоритетом, за которым следуют пользовательские пакеты сайтов, а системные пакеты сайта - последние.
Так что использовать?
Я думаю, что есть три варианта:
- использовать системную установку Python
- использовать обычный virtualenv
- использовать virtualenv с системными сайтами-пакетами
Я думаю, что в большинстве случаев, например, на обычных серверах/десктопах, изменения системы Python, как правило, следует избегать. По крайней мере, на * nix, слишком многое зависит от Python. Я бы очень не хотел менять свое поведение. Возможными исключениями являются эфемерные или статические "системы" (например, внутри контейнера).
Что касается виртуальных сред, если мы не знаем, что нам понадобятся системные пакеты сайтов, я думаю, что имеет смысл придерживаться текущей практики и использовать обычные. Если мы придерживаемся этого, то для внедрения кода перед выполнением нашего скрипта у нас есть только одна опция:
Добавить sitecustomize.py
в пакеты сайта virtuaelenv.
Убираться
# remove the virtualenvs
rm -rf my_venv
rm -rf venv_system
rm -rf venv_no_system
# remove our usercustomize.py and sitecustomize.py
sudo rm /usr/lib/python3.7/site-packages/sitecustomize.py
sudo rm /usr/lib/python3.7/site-packages/usercustomize.py
rm ~/.local/lib/python3.7/site-packages/sitecustomize.py
rm ~/.local/lib/python3.7/site-packages/usercustomize.py
# remove the modules
rm foo.py
rm my_app.py