Динамический импорт модулей с последующей реализацией объектов с определенным базовым классом из указанных модулей
Я пишу приложение. Нет графического интерфейса: s или что-то еще, просто старое консольное приложение. Это приложение, позвонив ему в приложение, должно иметь возможность загружать плагины при запуске. Итак, естественно, я создал класс для наследуемых плагинов:
class PluginBase(object):
def on_load(self):
pass
def on_unload(self):
pass
def do_work(self, data):
pass
Идея заключается в том, что при запуске приложение будет проходить через текущий каталог, включая поддиры, и искать модули, содержащие классы, которые сами по себе являются подклассами PluginBase
.
Больше кода:
class PluginLoader(object):
def __init__(self, path, cls):
""" path=path to search (unused atm), cls=baseclass """
self.path=path
def search(self):
for root, dirs, files in os.walk('.'):
candidates = [fname for fname in files if fname.endswith('.py') \
and not fname.startswith('__')]
## this only works if the modules happen to be in the current working dir
## that is not important now, i'll fix that later
if candidates:
basename = os.path.split(os.getcwd())[1]
for c in candidates:
modname = os.path.splitext(c)[0]
modname = '{0}.{1}'.format(basename, modname)
__import__(mod)
module = sys.modules[mod]
После этой последней строки в search
я хотел бы как-то a) найти все классы в недавно загруженном модуле, b) проверить, являются ли один или несколько из этих классов подклассами PluginBase
и c) (если b ) создайте экземпляр этого/этих классов и добавьте в список загруженных модулей приложения.
Я пробовал различные комбинации issubclass
и других, за которым следовал период интенсивного dir
: ing и около часа панически запущенного. Я нашел аналогичный подход к моему здесь, и я попробовал просто скопировать его, но получил сообщение о том, что Python не поддерживает импорт по имени файла, после чего я вид потерял мою концентрацию, и в результате этого этот пост был написан.
Я нахожусь на своем конце, все помогают оценить.
Ответы
Ответ 1
Вы можете сделать что-то вроде этого:
for c in candidates:
modname = os.path.splitext(c)[0]
try:
module=__import__(modname) #<-- You can get the module this way
except (ImportError,NotImplementedError):
continue
for cls in dir(module): #<-- Loop over all objects in the module namespace
cls=getattr(module,cls)
if (inspect.isclass(cls) # Make sure it is a class
and inspect.getmodule(cls)==module # Make sure it was defined in module, not just imported
and issubclass(cls,base)): # Make sure it is a subclass of base
# print('found in {f}: {c}'.format(f=module.__name__,c=cls))
classList.append(cls)
Чтобы проверить вышеизложенное, мне пришлось немного изменить код; ниже приведен полный script.
import sys
import inspect
import os
class PluginBase(object): pass
def search(base):
for root, dirs, files in os.walk('.'):
candidates = [fname for fname in files if fname.endswith('.py')
and not fname.startswith('__')]
classList=[]
if candidates:
for c in candidates:
modname = os.path.splitext(c)[0]
try:
module=__import__(modname)
except (ImportError,NotImplementedError):
continue
for cls in dir(module):
cls=getattr(module,cls)
if (inspect.isclass(cls)
and inspect.getmodule(cls)==module
and issubclass(cls,base)):
# print('found in {f}: {c}'.format(f=module.__name__,c=cls))
classList.append(cls)
print(classList)
search(PluginBase)
Ответ 2
Вы сделали бы это намного проще, если бы вы наложили ограничения на плагинов, например, что все плагины должны быть пакетами, которые содержат функцию load_plugin( app, config)
, которая возвращает экземпляр Plugin. Затем все, что вам нужно сделать, это попытаться импортировать эти пакеты и запустить функцию.
Ответ 3
Вот мета-классный способ регистрации плагинов:
Определите PluginBase
как тип PluginType
.
PluginType
автоматически регистрирует любой экземпляр (класс) в наборе plugins
.
plugin.py:
plugins=set()
class PluginType(type):
def __init__(cls, name, bases, attrs):
super(PluginType, cls).__init__(name, bases, attrs)
# print(cls, name,cls.__module__)
plugins.add(cls)
class PluginBase(object):
__metaclass__=PluginType
pass
Это часть, которую пользователь пишет. Обратите внимание, что здесь нет ничего особенного.
плагинов, /myplugin.py:
import plugin
class Foo(plugin.PluginBase):
pass
Вот что может выглядеть функция поиска:
test.py:
import plugin
import os
import imp
def search(plugindir):
for root, dirs, files in os.walk(plugindir):
for fname in files:
modname = os.path.splitext(fname)[0]
try:
module=imp.load_source(modname,os.path.join(root,fname))
except Exception: continue
search('pluginDir')
print(plugin.plugins)
Запуск результатов test.py
set([<class 'myplugin.Foo'>])
Ответ 4
Можете ли вы использовать execfile() вместо импорта с указанным пространством имен dict, затем перебрать это пространство имен с помощью isubclass и т.д.?