Python 插件系统 - 如何实现
我正在写一个Python应用程序,用来存储一些数据。为了存储这些数据,我创建了一个叫做Connection的类,这个类里面有一些抽象方法(使用Python的abc模块)。这个类是所有存储后端的父类,所有的存储后端都是从这个类派生出来的。每个存储后端只有一个目的,比如把数据存储在普通的文本文件里或者XML文件里。
所有的存储后端(包括父类所在的模块)都在一个叫做'data_handler'的包里。每个后端都在一个单独的模块中。
我的应用程序应该能够同时在多个后端存储数据,并且在运行时确定哪些存储后端是可用的。为此,我想到了写一个单例类,让每个后端在导入时进行注册。但我觉得在动态语言中这样做可能不是很好(如果我理解错了,请纠正我)。另一种方法是导入这个包,使用import data_handler
,然后获取这个包的__file__
属性,搜索目录下所有的Python文件,找出哪些是Connection类的子类。
我应该使用哪种方法,或者有没有其他(可能更好的)方法来实现这个?
Stefan
在运行时发现后端是一个严格的要求,还是在代码中静态列举它们就可以了?
这个功能很好,这样我在添加新的后端时就不需要修改代码了。
但是你的应用程序是否总是需要写入所有后端?
我会有一个类,可以在其中注册可用的处理器。数据将写入每个注册的处理器。但并不是所有可用的处理器都必须注册。
4 个回答
你可以使用这样的一个函数:
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是你想要加载的类的完整名称,通常是用点号分隔的。
举个例子(伪代码,但思路是这样的):
在检查包是否可用时,只需要进行一些简单的匹配,然后为了找到最终的类名,你可以在每个模块中声明一个Plugin类,并且这个类里有一个getStorage()的方法。
#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)
这样,你就可以动态地扫描你现有的存储插件了。
如果这些后端要在不同的Python版本中分发,你可能需要了解一下setuptools或distribute的入口点。这里有一篇文章,介绍了如何利用这些工具来动态寻找插件:
http://aroberge.blogspot.com/2008/12/plugins-part-6-setuptools-based.html
千万不要去遍历文件系统,去扫描后端的Python源代码!这是一种很糟糕的做法,尤其在这里根本不需要这样做!在导入时注册所有的类是完全可以的。
把后端存储在类属性中,而不是实例属性中;这样,所有的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>}