Python插件系统 - HOWTO

时间:2010-09-07 14:51:27

标签: python

我正在编写一个存储一些数据的Python应用程序。为了存储数据,我用抽象方法编写了一个Connection类(使用Python的abc模块)。这个类是所有存储后端派生自的超类。每个存储后端只有一个目的,例如,将数据存储在纯文本文件或XML文件中。

所有存储后端(包括超类所在的模块)都在一个名为“data_handler”的包中。每个后端都在一个模块中。

我的应用程序应该能够同时在多个后端存储数据,并在运行时确定哪些存储后端可用。为此,我想写一个单例类,每个后端必须在导入时注册。但这在动态语言中似乎不太好(如果我误解了这一点,请纠正我)。另一种方法是使用import data_handler导入包,然后获取包的__file__属性,并在dir中搜索超级连接类的子类的所有Python文件。

我应该使用什么方法,或者还有其他(可能更好)的方法来执行此操作。

的Stefan


  

在运行时发现严格要求的后端或静态   在代码中枚举它们吗?

当我添加新的后端

时,这个功能非常值得注意编辑代码
  

但是,您的应用程序是否应始终写入所有后端?

我将有一个可以注册可用处理程序的类。并且数据应写入每个注册的处理程序。但并非所有可用的处理程序都必须注册。

4 个答案:

答案 0 :(得分:3)

执行文件系统(!)并扫描后端的Python源代码!在最好的时候,这是一个丑陋的黑客,更糟糕的是,因为你根本不需要任何类似的东西!在导入时注册所有类是完全可以的。


将后端存储在class属性中而不是实例属性中;这样,所有Storage个实例都将查看同一组后端:

>>> class Storage(object):
...     backends = set()
...
...     def register(self, backend):
...             self.backends.add(backend)
...

每个后端都可以通过实例化自己的Storage来注册自己,backends可以访问类级>>> foo = Storage() >>> foo.register("text") >>> bar = Storage() >>> bar.register("xml") 属性:

Storage

您可以通过实例化另一个>>> baz = Storage() >>> baz.backends {'xml', 'text'} 来读取此属性,这将读取相同的变量:

Connection

您甚至可以将后端实例存储在>>> class Connection(object,metaclass=abc.ABCMeta): ... @abc.abstractmethod ... def register(self, backend): ... pass ... ... backends = set() ... >>> class TextBackend(Connection): ... def register(self): ... super().backends.add(self) ... ... def __init__(self): ... self.register() ... >>> class XMLBackend(Connection): ... def register(self): ... super().backends.add(self) ... ... def __init__(self): ... self.register() ... >>> foo = TextBackend() >>> bar = XMLBackend() >>> Connection.backends {<__main__.XMLBackend object at 0x027ADAB0>, \ <__main__.TextBackend object at 0x027ADA50>} 的类属性中,并在实例化时注册每个后端:

{{1}}

答案 1 :(得分:1)

如果要在各种Python发行版中分发这些后端,您可能需要查看setuptools / distribute入口点。这是一篇关于如何将它们用于动态插件查找服务的文章:

http://aroberge.blogspot.com/2008/12/plugins-part-6-setuptools-based.html

答案 2 :(得分:0)

但是,您的应用始终是否应该写入所有后端?如果没有,你可以使用(像往常一样)另一层间接,例如

storage = Storage()
storage.use(TextBackend, XMLBackend, YamlBackend)
storage.write(data)

或类似的东西,Storage调度程序,它只是循环后端并调用适当的序列化程序。

这当然非常粗糙。

答案 3 :(得分:0)

你可以使用像这样的函数:

def loadClass(fullclassname):
    sepindex=fullclassname.rindex('.')    
    classname=fullclassname[sepindex+1:]
    modname=fullclassname[:sepindex]
    #dynmically import the class in the module
    imod=__import__(modname,None,None,classname)
    classtype=getattr(imod,classname)
    return classtype

其中fullclassname是要加载的类的完全虚线限定符。

示例(伪代码,但想法就在那里):

对于软件包可用性扫描,只执行一些通配,然后为了查找最终类名,您可以在每个具有getStorage()的模块中声明一个Plugin类

 #scan for modules , getPluginPackagesUnder (to be defined) returns the dotted name for all packages under a root path (using some globbing, listdir or whatever method) 

    pluginpackages=getPluginPackagesUnder("x/y/z")
    storagelist=[]
    for pgpck in plunginpackages:
        pluginclass=loadClass("%s.Plugin"%pgpck)
        storageinstance=Plugin().getStorage()
        storagelist.append(storageinstance)

因此,您可以动态扫描现有的存储插件