【问题标题】:Dynamic importing of modules followed by instantiation of objects with a certain baseclass from said modules动态导入模块,然后从所述模块实例化具有特定基类的对象
【发布时间】:2011-01-24 21:19:56
【问题描述】:

我正在编写一个应用程序。没有花哨的 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:ing 和大约一个小时的恐慌谷歌搜索。我确实找到了一种与我的here 类似的方法,我尝试只是复制粘贴,但得到一个错误,说 Python 不支持按文件名导入,此时我有点失去了注意力,结果,这篇文章是写的。

我在这里无所适从,感谢所有帮助。

【问题讨论】:

    标签: python plugins


    【解决方案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'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)
    

    【讨论】:

    • 它工作得几乎完美,谢谢!我添加了and cls.__name__ != base.__name__ 以避免将基类添加到子类列表中。
    【解决方案2】:

    如果你对插件编写器施加一些限制,你会更容易做到这一点,例如,所有插件必须是包含返回插件实例的load_plugin( app, config) 函数的包。然后你所要做的就是尝试导入这些包并运行函数。

    【讨论】:

      【解决方案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
      

      这是用户写的部分。请注意,这里没有什么特别之处。

      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'>])
      

      【讨论】:

        【解决方案4】:

        您可以使用 execfile() 代替 import 指定命名空间字典,然后使用 issubclass 等迭代该命名空间吗?

        【讨论】:

          猜你喜欢
          • 2021-06-25
          • 1970-01-01
          • 2022-08-22
          • 1970-01-01
          • 1970-01-01
          • 2019-03-02
          • 2011-05-06
          • 2020-06-18
          • 1970-01-01
          相关资源
          最近更新 更多