Проблема динамического импорта модулей в Python 3
У меня есть ситуация, когда в моем проекте Python 3 во время выполнения должны быть включены определенные модули. я использую importlib.import_module
за это.
ВТОРОЕ ОБНОВЛЕНИЕ: я нашел способ сделать что-то близкое к тому, что я хотел. Некоторый дополнительный код, возможно, немного отрисовал мои ссылки здесь. Я отправлю ответ, чтобы показать, что я сделал.
ПЕРВОЕ ОБНОВЛЕНИЕ: через различные группы Python мне сказали, что это невозможно. У меня есть раздел обновления в нижней части.
Настройка
Идея состоит в том, что когда пользователь запускает мой скрипт, любые файлы, называемые "агентами _*. Py" (где * заменяется некоторым словом), ищут классы, и они импортируются. Так, например, файл с именем agents_human.py
будет иметь класс под названием HumanAgent
, Это то HumanAgent
класс, который будет создан, поэтому мне нужно импортировать модуль (файл), чтобы это могло произойти.
Основная проблема
Вопрос в два раза:
- Я не могу заставить этот модуль смотреть в нескольких каталогах.
- Когда моя программа установлена, модуль импорта вообще не работает.
Проблемный контекст
У меня есть база кода, которая показывает, что я пытаюсь сделать: https://github.com/jeffnyman/pacumen
Проблема в методе load_agent(); конкретно это утверждение.
Чтобы показать это, если вы клонируете этот репозиторий, вы можете запустить следующую команду из корня проекта:
python3 -m pacumen
- Это будет работать, если
agents_human.py
файл находится в корне проекта. - Это не будет работать, если
agents_human.py
файл являетсяagents
каталог в рамках проекта.
В моем репо кода agents_human.py
файл находится в agents
каталог и, следовательно, импорт не удается. Но если этот файл был перемещен в корень, вы увидите, что импорт работает. Тем не менее, как можно видеть в load_agent()
Функция, я добавил текущий рабочий каталог плюс каталог "агенты" к пути ( строки 119 - 121)
Когда я говорю, что импорт не удается, я имею в виду буквально, что: когда вызывается логика для импорта, ImportError
исключение срабатывает. Опять же, это только когда agents_human.py
файл находится в agents
каталог.
Проблема должна быть решена
В идеале я хотел бы, чтобы это работало для обоих подходов: когда файл находится в корне и когда в agents
каталог.
Другой проблемный контекст
Я просто собирался жить с необходимостью поместить файлы в корень. Но, что еще хуже, когда программа установлена (в отличие от запуска из корня проекта), даже та часть, которая работает выше, больше не работает.
В частности, из корня проекта клонированного проекта я делаю это:
pip3 install .
Затем я создаю какой-то каталог (скажем, test_pacumen
). Там я положил agents_human.py
файл с единственным содержимым:
class HumanAgent:
pass
(Также требуется layouts
каталог из моего проекта.)
Тогда я бегу:
pacumen
В этом контексте логика никогда не будет импортировать agents_human.py
файл... даже если он находится в корне того места, откуда выполняется команда.
Что я пробовал
Насколько я понимаю, рассматривая различные примеры, я должен импортировать через относительные имена или предоставлять явную привязку. Но, похоже, это происходит путем предоставления пути, по которому ищут модули. Код, на который я ссылался выше, похоже, делает это.
Я получаю имена модулей, которые находятся в пути (через эту строку), и во всех случаях он находит соответствующие файлы, которые я помещаю на место. Так что, кажется, путь работает; это не импорт. И вот где я не нахожу много указаний.
Я также попытался получить конечную часть пути для каждого пути к файлу следующим образом:
module_end_dir = os.path.basename(os.path.normpath(module_dir))
Затем, с этим на месте, я попробовал это:
agent_module = importlib.import_module(f"{module_end_dir}.{module_name[:-3]}")
Это работает локально, когда agents_human.py
находится в agents
каталог, но тогда не работает, если agents_human.py
в корне. И это все еще не работает вообще, когда программа установлена.
Краткое изложение проблем
В контексте первой проблемы (неустановленный контекст) я не знаю, почему
agents_human.py
модуль не может быть импортирован изagents
но его можно импортировать из корня.Во втором контексте (установлено), похоже,
agents_human.py
модуль не может быть импортирован, даже если он находится в корне.
Я надеюсь, что то, что я предоставил здесь, не слишком смущает.
ОБНОВИТЬ:
Мне предложили попробовать что-то подобное для моего load_agent
функция:
def load_agent(pacman, not_human):
module = importlib.import_module("agents.agents_human")
return getattr(module, pacman)
Это работает, когда программа запускается через корневой каталог проекта, но не работает, когда проект устанавливается через pip. Очевидно, это связано с точками входа в сценарии и с тем, как нельзя заставить Python распознавать каталоги как модули в этом контексте.
Был предоставлен еще один пример, который я понял так:
def load_agent(pacman, not_human):
import re
pysearchre = re.compile('.py$', re.IGNORECASE)
agent_files = filter(pysearchre.search, os.listdir(os.path.join(os.getcwd(), 'agents')))
form_module = lambda fp: '.' + os.path.splitext(fp)[0]
agents = map(form_module, agent_files)
importlib.import_module('agents')
for agent in agents:
if not agent.startswith('__'):
agent_module = importlib.import_module(agent, package="agents")
if pacman in dir(agent_module):
return getattr(agent_module, pacman)
raise Exception("The agent " + pacman + " is not specified in any agents_*.py file.")
Такая же ситуация. Это работает с программой при запуске без ее установки; это не когда программа установлена.
Это было в основном служить моим подтверждением того, что то, что я хочу сделать, невозможно, когда программа Python установлена через pip. Я не совсем уверен, но я включаю это обновление на случай, если другие столкнутся с этим.
2 ответа
То, что вы делаете, не будет работать из-за ваших точек входа в setup.py
но и потому, что Python не имеет понятия о вашем agents
каталог при установке программы.
Вы можете попробовать добавить путь к агенту в sys.path:
sys.path.insert(0, os.getcwd() + '/agents')
Но я сомневаюсь, что это сработает, когда кто-то установит вашу программу. (И вам это не нужно, когда программа запускается из проекта, как вы нашли.) Установка через pip в основном отделяет исполняемый скрипт от контекста каталога, когда вы запускаете его непосредственно из самого проекта.
Другая вещь, которую вы можете попробовать - это просто импортировать через местоположение файла. Другими словами, не относитесь к своим agents
каталог (из установки) в качестве фактического модуля. Скорее, просто относитесь к нему так, как оно есть: к каталогу. Проблема в том, что это еще одна область, где Python удается испортить вещи между версиями.
Если вы используете 3.5 и выше, вы можете сделать что-то вроде этого:
import importlib.util
spec = importlib.util.spec_from_file_location("agents.agent_human", "/agents/agents_human.py")
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
Если вы используете Python 3.3 или 3.4, вы можете сделать это:
from importlib.machinery import SourceFileLoader
foo = SourceFileLoader("agents.agent_human", "/agents/agents_human.py").load_module()
Но обратите внимание, что даже в Python 3.4 это не рекомендуется.
Вы можете попробовать некоторые варианты этих. Вам, вероятно, придется добавить некоторую логику для проверки любых файлов в agents
каталог, при условии, что вы хотите попытаться найти какой-либо класс среди всех файлов в этом каталоге.
Я нашел решение, которое в принципе работает, но мне пришлось немного пересмотреть свои требования. Первоначально я хотел, чтобы кто-то мог установить мой проект pacumen и найти определенные файлы агента либо в корневом каталоге проекта, либо в agents
каталог в корне проекта.
Проблема заключалась в том, что хотя это будет работать с локальной копией проекта, если проект был установлен через pip
, agents
каталог не может быть импортирован, потому что он не был распознан.
В итоге я согласился с тем, что у меня будут только файлы с шаблоном agents_*.py
в корне проекта. Я смог заставить это работать буквально с добавлением одной строки к моему load_agents()
метод как таковой:
sys.path.insert(0, os.getcwd())
Вот этот метод с рассматриваемой строкой. Это в сочетании с кодом, который у меня уже был, позволил этому работать нормально.