Динамическая загрузка классов в Python 2.6: RuntimeWarning: плагины родительского модуля не найдены при обработке абсолютного импорта - PullRequest
23 голосов
/ 15 февраля 2010

Я работаю над системой плагинов, где модули плагинов загружаются так:

def load_plugins():
   plugins=glob.glob("plugins/*.py")
   instances=[]
   for p in plugins:
      try:
         name=p.split("/")[-1]
         name=name.split(".py")[0]
         log.debug("Possible plugin: %s", name)
         f, file, desc=imp.find_module(name, ["plugins"])
         plugin=imp.load_module('plugins.'+name, f, file, desc)
         getattr(plugin, "__init__")(log)
         instances=instances+plugin.get_instances()
      except Exception as e:
         log.info("Failed to load plugin: "+str(p))
         log.info("Error: %s " % (e))
         log.info(traceback.format_exc(e))
   return instances

Код работает, но для каждого оператора импорта в коде плагина я получаю следующее предупреждение:

plugins/plugin.py:2: RuntimeWarning: Parent module 'plugins' not found while handling absolute import
  import os

Не сообщается об ошибках для основного кода программы, и плагины работают.

Может кто-нибудь объяснить, что означает предупреждение и что я делаю неправильно. Нужно ли создавать пустой модуль плагинов отдельно и импортировать его, чтобы Python был доволен?

Ответы [ 5 ]

16 голосов
/ 15 февраля 2010

Если в каталоге плагинов нет __init__.py, это не пакет, поэтому, когда вы создаете plugins.whatever, Python предупреждает вас, что такого не должно быть на самом деле.(Он не может быть создан "import plugins.whatever" независимо от вашего пути.)

Кроме того,

  • Не делиться на /, что невозможно переносить,Используйте os.path.split.
  • Не используйте .split(".py"), чтобы получить имя без расширения, которое содержит ошибки.Используйте os.path.splitext.
  • Не используйте getattr со строковым литералом.getattr(plugin, "__init__") пишется plugin.__init__.
  • Я запутался, почему вы вызываете функцию уровня модуля __init__.Это не кажется правильным.Возможно, вы хотите использовать функцию set_logger или лучше для создания экземпляра класса, который использует регистратор.
  • Не используйте L = L + some_other_list для расширения списка, используйте метод extend, который имеет лучшую производительность иболее идиоматичен.
  • Не задавайте неизвестные исключения на except Exception.Если вы не можете планировать что-то вменяемое в ответ на исключение, ваша программа не может продолжать вменяемо.
14 голосов
/ 15 февраля 2010

Если бы каталог plugins был реальным пакетом (содержал __init__.py отлично), вы могли бы легко использовать pkgutils для перечисления файлов его плагинов и загрузки их.

import pkgutil
# import our package
import plugins
list(pkgutil.iter_modules(plugins.__path__))

Однако, в любом случае, он может работать без пакета плагинов, попробуйте это:

import pkgutil
list(pkgutil.iter_modules(["plugins"]))

Также возможно создать пакет, который существует только во время выполнения:

import types
import sys
plugins = types.ModuleType("plugins")
plugins.__path__ = ["plugins"]

sys.modules["plugins"] = plugins
import plugins.testplugin

Тем не менее, этот хак был в основном для развлечения!

6 голосов
/ 18 октября 2014

Проблема здесь с точкой ('.') В имени модуля:

imp.load_module('plugins.'+name, f, file, desc)

Не включать «.» после 'plugins', или Python подумает, что это путь к модулю.

2 голосов
/ 07 декабря 2018

Вы можете попробовать добавить приведенный ниже оператор в начале операторов импорта.

from __future__ import absolute_import
0 голосов
/ 22 сентября 2016

Документация Python imp была обновлена ​​с момента получения ответа.Теперь она решает эту проблему конкретно в методе find_module().

Эта функция не обрабатывает иерархические имена модулей (имена, содержащие точки).Чтобы найти PM , то есть субмодуль M пакета P , используйте find_module() и load_module() для поиска и загрузки пакета P , а затем используйте find_module() с аргументом path , установленным в P.__path__.Если P имеет точечное имя, примените этот рецепт рекурсивно.

Обратите внимание, что P.__path__ уже является списком при его передаче find_module(),Также обратите внимание, что в документации find_module() говорится о поиске пакетов.

Если модуль представляет собой пакет, file равен None, путь - это путь к пакету, а последний элемент в описании - PKG_DIRECTORY.

Таким образом, из вопроса ОП, чтобы импортироватьплагин без предупреждений RuntimeError, следуйте инструкциям в обновленной документации Python:

# first find and load the package assuming it is in
# the current working directory, '.'

f, file, desc = imp.find_module('plugins', ['.'])
pkg = imp.load_module('plugins', f, file, desc)

# then find the named plugin module using pkg.__path__
# and load the module using the dotted name

f, file, desc = imp.find_module(name, pkg.__path__)
plugin = imp.load_module('plugins.' + name, f, file, desc)
...