Проблема динамического импорта модулей в Python 3

У меня есть ситуация, когда в моем проекте Python 3 во время выполнения должны быть включены определенные модули. я использую importlib.import_module за это.


ВТОРОЕ ОБНОВЛЕНИЕ: я нашел способ сделать что-то близкое к тому, что я хотел. Некоторый дополнительный код, возможно, немного отрисовал мои ссылки здесь. Я отправлю ответ, чтобы показать, что я сделал.



ПЕРВОЕ ОБНОВЛЕНИЕ: через различные группы Python мне сказали, что это невозможно. У меня есть раздел обновления в нижней части.


Настройка

Идея состоит в том, что когда пользователь запускает мой скрипт, любые файлы, называемые "агентами _*. Py" (где * заменяется некоторым словом), ищут классы, и они импортируются. Так, например, файл с именем agents_human.py будет иметь класс под названием HumanAgent, Это то HumanAgent класс, который будет создан, поэтому мне нужно импортировать модуль (файл), чтобы это могло произойти.

Основная проблема

Вопрос в два раза:

  1. Я не могу заставить этот модуль смотреть в нескольких каталогах.
  2. Когда моя программа установлена, модуль импорта вообще не работает.

Проблемный контекст

У меня есть база кода, которая показывает, что я пытаюсь сделать: 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 в корне. И это все еще не работает вообще, когда программа установлена.

Краткое изложение проблем

  1. В контексте первой проблемы (неустановленный контекст) я не знаю, почему agents_human.py модуль не может быть импортирован из agents но его можно импортировать из корня.

  2. Во втором контексте (установлено), похоже, 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())

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

Другие вопросы по тегам