动态导入模块并实例化具有特定基类的对象
我正在写一个应用程序。没有花哨的图形界面,就是一个普通的控制台应用程序。这个应用程序,我们称之为App,需要在启动时加载插件。所以,我自然创建了一个插件的基类,让其他插件可以从这个类继承:
class PluginBase(object):
def on_load(self):
pass
def on_unload(self):
pass
def do_work(self, data):
pass
我的想法是,在启动时,App会遍历当前目录,包括子目录,寻找那些包含类的模块,而这些类本身是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)(如果有子类的话)实例化这些类,并将它们添加到App的已加载模块列表中。
我尝试了各种issubclass
和其他方法的组合,接着又经历了一段时间的dir
调试和大约一个小时的慌乱搜索。我确实找到了一种与我类似的方法,在这里,我试着直接复制粘贴,但遇到了一个错误,提示Python不支持按文件名导入,这让我有点失去专注,结果写下了这篇帖子。
我现在真的很困惑,任何帮助都非常感谢。
4 个回答
这里有一种更高级的方式来注册插件:
首先定义一个叫做 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
这部分是用户自己写的。注意这里没有什么特别的地方。
pluginDir/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'>])
如果你给插件开发者设定一些规则,这样会简单很多。例如,要求所有插件都是包含一个叫做 load_plugin( app, config)
的函数的包,这个函数会返回一个插件实例。这样你只需要尝试导入这些包,然后运行这个函数就可以了。
你可以这样做:
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's 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)
为了测试上面的内容,我需要稍微修改一下你的代码;下面是完整的脚本。
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)