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