2010-09-07 39 views
2

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

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

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

我应该使用什么方法,或者有其他方法(也许更好)来做到这一点。

斯特凡


在运行时被发现的后端严格的要求或将静态代码 列举他们的呢?

此功能将是不错的注意必须编辑代码时,我添加一个新的后端


但要你的应用程序总是写所有的后台?

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

+0

在运行时被发现的后端严格的要求或将在他们的代码静态枚举吗? – Arlaharen 2010-09-07 15:03:21

+0

嗨Stefan,我冒昧地合并了你在这里的2个未注册账户,以及那些声誉和信息。您现在应拥有您在SO上发布的所有内容。如果您的系统中包含更多未注册的帐户,只需将其标记为主持人即可合并。干杯。 – 2011-12-02 07:33:15

回答

0

但是,您的应用程序总是写入全部后端?如果不是,你可以使用(像往常一样)另一个间接层,例如

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

或者这样的事情,与Storage是一个调度,这只是在后端循环并调用相应的串行器。

这当然很粗糙。

3

不是行走文件系统(!)并扫描后端的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>} 
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是完全虚线qualifiant的类你想要加载。

例子(伪代码,但这个想法是有):

的包可用性扫描,只能执行一些通配,然后寻找最后的类名,您可以在每个模块的声明插件类,它具有一个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) 

的话,你可以动态扫描现有的存储插件

+0

这是一个讨厌的黑客,但有一件事特别令人烦恼 - 你知道你可以做'modname,classname = fullclassname.rsplit(“。”,1)'? :p – katrielalex 2010-09-07 15:26:50

+0

事实上,更pythonic。这不是一个讨厌的黑客攻击,它利用了python的动态加载能力,并且把一个Plugin类放在只是一个方便的捷径,因为没有代码来发现最终的类名。 – dweeves 2010-09-07 15:32:07