Проблема динамического импорта модулей в Python 3 - PullRequest
0 голосов
/ 09 января 2019

У меня есть ситуация, когда в моем проекте 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 не может быть импортирован, даже если он находится в корне.

Я надеюсь, что то, что я привел здесь, не слишком запутанно.

UPDATE:

Мне предложили попробовать что-то подобное для моей 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 ]

0 голосов
/ 12 января 2019

Я нашел решение, которое в принципе работает, но мне пришлось немного пересмотреть свои требования. Первоначально я хотел, чтобы кто-то мог установить мой проект pacumen и найти определенные файлы агента в корневом каталоге проекта или в каталоге agents в корневом каталоге проекта.

Проблема заключалась в том, что, хотя это работало бы с локальной копией проекта, если проект был установлен через pip, каталог agents не мог быть импортирован, поскольку он не был распознан.

В конце концов я согласился с тем, что у меня будут только файлы с шаблоном agents_*.py в корне проекта. Я смог заставить это работать буквально с добавлением одной строки в мой load_agents() метод как таковой:

sys.path.insert(0, os.getcwd())

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

0 голосов
/ 11 января 2019

То, что вы делаете, не будет работать из-за ваших точек входа в 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, при условии, что вы хотите попытаться найти какой-либо класс среди всех файлов в этом каталоге.

...