我有一个用Python编写的应用程序,它是由相当专业的观众(科学家)使用的。
我正在寻找一个好方法,使应用程序可扩展的用户,即脚本/插件架构。
I am looking for something extremely lightweight. Most scripts, or plugins, are not going to be developed and distributed by a third-party and installed, but are going to be something whipped up by a user in a few minutes to automate a repeating task, add support for a file format, etc. So plugins should have the absolute minimum boilerplate code, and require no 'installation' other than copying to a folder (so something like setuptools entry points, or the Zope plugin architecture seems like too much.)
是否已经有类似的系统存在,或者是否有项目实现了类似的方案,我应该看看想法/灵感?
我花了很多时间试图找到适合我需求的小型Python插件系统。但后来我就想,既然已经有了一种遗传,它是自然的、灵活的,为什么不利用它呢?
对插件使用继承的唯一问题是你不知道最具体的(继承树中最低的)插件类是什么。
但这可以用元类来解决,它可以跟踪基类的继承,也可以构建类,它继承自大多数特定的插件(下图中的“根扩展”)
所以我通过编写这样一个元类来解决这个问题:
class PluginBaseMeta(type):
def __new__(mcls, name, bases, namespace):
cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
if not hasattr(cls, '__pluginextensions__'): # parent class
cls.__pluginextensions__ = {cls} # set reflects lowest plugins
cls.__pluginroot__ = cls
cls.__pluginiscachevalid__ = False
else: # subclass
assert not set(namespace) & {'__pluginextensions__',
'__pluginroot__'} # only in parent
exts = cls.__pluginextensions__
exts.difference_update(set(bases)) # remove parents
exts.add(cls) # and add current
cls.__pluginroot__.__pluginiscachevalid__ = False
return cls
@property
def PluginExtended(cls):
# After PluginExtended creation we'll have only 1 item in set
# so this is used for caching, mainly not to create same PluginExtended
if cls.__pluginroot__.__pluginiscachevalid__:
return next(iter(cls.__pluginextensions__)) # only 1 item in set
else:
name = cls.__pluginroot__.__name__ + 'PluginExtended'
extended = type(name, tuple(cls.__pluginextensions__), {})
cls.__pluginroot__.__pluginiscachevalid__ = True
return extended
所以当你有根基础,用元类,并有一棵从它继承的插件树时,你可以自动获得class,它通过子类化从最特定的插件继承:
class RootExtended(RootBase.PluginExtended):
... your code here ...
代码库非常小(大约30行纯代码),并且继承时非常灵活。
如果你感兴趣,可以@ https://github.com/thodnev/pluginlib参与进来
实际上,setuptools工作在一个“插件目录”下,下面的例子摘自项目文档:
http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins
使用示例:
plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions) # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)
从长远来看,setuptools是一个更安全的选择,因为它可以加载插件而不会发生冲突或丢失需求。
另一个好处是插件本身可以使用相同的机制进行扩展,而无需原始应用程序关心它。
在我们当前的医疗保健产品中,我们有一个用接口类实现的插件体系结构。我们的技术栈是Django在Python之上的API, Nuxtjs在nodejs之上的前端。
我们为我们的产品编写了一个插件管理器应用程序,它基本上是pip和npm包,遵循Django和Nuxtjs。
对于新插件开发(pip和npm),我们将插件管理器作为依赖项。
在Pip软件包中:
在setup.py的帮助下,你可以添加插件的入口点,用插件管理器做一些事情(注册表,初始化,等等)。
https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation
在npm包中:
与pip类似,npm脚本中有钩子来处理安装。
https://docs.npmjs.com/misc/scripts
我们的usecase:
现在插件开发团队与核心开发团队是分开的。插件开发的范围是与第三方应用程序集成,这些应用程序定义在产品的任何类别中。插件接口分类如下:-传真,电话,电子邮件…等插件管理器可以增强到新的类别。
在你的情况下:也许你可以写一个插件,然后重复使用它来做一些事情。
如果插件开发人员需要使用重用核心对象,可以通过在插件管理器中进行抽象级别来使用该对象,以便任何插件都可以继承这些方法。
只是分享一下我们是如何在我们的产品中实现的,希望能给大家一点启发。
你也可以看看“基础工作”。
其思想是围绕可重用组件(称为模式和插件)构建应用程序。插件是派生自GwBasePattern的类。
这里有一个基本的例子:
from groundwork import App
from groundwork.patterns import GwBasePattern
class MyPlugin(GwBasePattern):
def __init__(self, app, **kwargs):
self.name = "My Plugin"
super().__init__(app, **kwargs)
def activate(self):
pass
def deactivate(self):
pass
my_app = App(plugins=[MyPlugin]) # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it
还有更高级的模式来处理命令行接口、信号或共享对象。
foundation可以通过编程方式将插件绑定到应用程序中,如上面所示,也可以通过setuptools自动查找插件。包含插件的Python包必须使用一个特殊的入口点groundwork.plugin声明这些插件。
这是文件。
免责声明:我是《基础》的作者之一。