我正在写一个应用程序。没有花哨的GUI:s或任何东西,只是一个普通的旧控制台应用程序。这个应用程序,让我们称之为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的子类)(如果b)实例化那些/那些类并添加到App的已加载模块列表中。
我尝试了issubclass
和其他人的各种组合,然后是一段激烈的dir
:大约一个小时的恐慌谷歌搜索。我确实找到了类似于我here的方法,我尝试了复制粘贴,但得到一个错误,说Python不支持按文件名导入,此时我有点失去了我的注意力,并且由于那篇文章是写的。
我在这里的斗智尽头,所有人都很感激。
答案 0 :(得分:4)
您可能会这样做:
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)
答案 1 :(得分:3)
如果你对插件编写器施加了一些约束,你会使这更容易,例如所有插件必须是包含返回插件实例的load_plugin( app, config)
函数的包。然后,您所要做的就是尝试导入这些包并运行该功能。
答案 2 :(得分:2)
这是一种注册插件的元分类方法:
将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 yield
set([<class 'myplugin.Foo'>])
答案 3 :(得分:0)
您是否可以使用execfile()而不是使用指定的命名空间dict导入,然后使用issubclass等迭代该命名空间?