动态导入模块,然后从所述模块实例化具有特定基类的对象

时间:2011-01-24 21:19:57

标签: python plugins

我正在写一个应用程序。没有花哨的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不支持按文件名导入,此时我有点失去了我的注意力,并且由于那篇文章是写的。

我在这里的斗智尽头,所有人都很感激。

4 个答案:

答案 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等迭代该命名空间?