Получи случайную криптовалюту за регистрацию!

Релятивный импорт в исполняемом файле Часто встречается ситуа | Python Заметки

Релятивный импорт в исполняемом файле

Часто встречается ситуация, когда исполняемый скрипт находится внутри Python-пакета. Например, представим такую структуру библиотеки:

my_lib/
cmd/
start.py
stop.py
core.py
services.py

Для запуска каких-то процессов мне надо исполнить скрипт start.py и вот как я делаю его вызов:

python3 /mnt/libs/my_lib/cmd/start.py

Пока выглядит всё красиво.
Но что, если я внутри этого файла хочу импортировать модуль services.py? При этом я хочу использовать релятивный импорт

# start.py
if __name__ == "__main__":
from .. import services

Я получу такую ошибку:

ValueError: attempted relative import beyond top-level package

Эта ошибка возникает потому, что интерпретатор просто не знает что мы находимся внутри пакета и не может понять куда это мы собрались выйти на уровень выше)

Есть три способа как избежать этой ошибки. Все они требуют чтобы библиотека my_lib находилась в доступном для импорта месте, то есть в моëм случае чтобы путь /mnt/libs был в sys.path.

Просто пишем полный путь импорта

if __name__ == "__main__":
from my_lib import services

Это сработает. Но, очевидно, что это не то, что мы ищем. Нам нужен релятивный импорт.

Если интерпретатору подсказать имя пакета в котором мы находимся, то всё заведётся. И есть два способа это сделать. Первый способ — это запускать не через имя файла а по имени модуля

python -m my_lib.cmd.start

Уже самой командой мы обозначили все необходимые неймспейсы.

Если предыдущий способ недоступен (то есть запускаем именно по пути к файлу .../start.py), то объявляем имя пакета прямо внутри кода. Для этого используем переменную модуля __package__

if __name__ == "__main__":
if __package__ is None:
__package__ = 'my_lib.cmd'
from .. import services

Кстати, мы также можем при необходимости:
динамически определить имя пакета в котором находимся
добавить необходимые пути к основной библиотеке в sys.path перед импортом
переместить обновление __package__ в начале скрипта вместе со всеми импортами но обязательно с проверкой is None!

#tricks