Python插件系统-HOWTO

Python插件系统-HOWTO,python,Python,我正在编写一个Python应用程序来存储一些数据。为了存储数据,我编写了一个带有抽象方法的连接类(使用Python的abc模块)。此类是所有存储后端派生的超级类。每个存储后端只有一个用途,例如,将数据存储在纯文本文件或XML文件中 所有存储后端(包括超级类所在的模块)都位于一个名为“data_handler”的包中。每个后端都在一个模块中 我的应用程序应该能够在多个后端同时存储数据,并在运行时确定哪些存储后端可用。为此,我想编写一个单例类,其中每个后端必须在导入时注册。但这在动态语言中似乎不太好

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

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

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

我应该使用什么方法,或者是否有其他(可能更好)方法来实现这一点

斯特凡



在运行时发现后端是严格要求还是静态要求 它们在代码中的枚举是什么

当我添加一个新的后端时,如果我不得不编辑代码,这个特性会很好



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


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

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

storage = Storage()
storage.use(TextBackend, XMLBackend, YamlBackend)
storage.write(data)
或者类似的东西,使用
存储
作为调度程序,它只需在后端循环并调用适当的序列化程序


这当然是非常粗糙的。

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


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

>>> class Storage(object):
...     backends = set()
...
...     def register(self, backend):
...             self.backends.add(backend)
...
每个后端都可以通过实例化自己的
存储
来注册自己,该存储可以访问类级别的
后端
属性:

>>> foo = Storage()
>>> foo.register("text")
>>> bar = Storage()
>>> bar.register("xml")
通过实例化另一个
存储
,可以读取此属性,该存储将读取相同的变量:

>>> baz = Storage()
>>> baz.backends
{'xml', 'text'}

您甚至可以将后端实例存储在
Connection
的class属性中,并在实例化时注册每个后端:

>>> 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>}
类连接(对象,元类=abc.ABCMeta): ... @抽象方法 ... def寄存器(自身、后端): ... 通过 ... ... 后端=集合() ... >>>类TextBackend(连接): ... def寄存器(自): ... super().backends.add(self) ... ... 定义初始化(自): ... self.register() ... >>>类XMLBackend(连接): ... def寄存器(自): ... super().backends.add(self) ... ... 定义初始化(自): ... self.register() ... >>>foo=TextBackend() >>>bar=XMLBackend() >>>连接.后端 {, \ }
如果这些后端将分布在各种Python发行版中,您可能需要查看setuptools/distribute入口点。下面是一篇文章,介绍如何将这些用于动态插件查找服务:


您可以使用如下功能:

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是要加载的类的全虚线限定符

示例(伪代码,但其思想是存在的):

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


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

在运行时发现后端是一项严格的要求,还是在代码中对其进行静态枚举?嗨,Stefan,我冒昧地合并了您在这里拥有的两个未注册帐户,其中包含声誉和信息。你现在应该拥有你发布的所有内容。如果系统中有更多未注册的帐户包含内容,只需将其标记为版主也可以合并这些内容。干杯。这有点讨厌,但有一件事特别让我恼火——你知道你可以做
modname,classname=fullclassname.rsplit(“.”,1)
:皮迪德,更像蟒蛇。这不是一个令人讨厌的黑客行为,它利用了python的动态加载能力,并且放置一个插件类只是一个方便的快捷方式,不需要代码来发现最终的类名。
 #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)