我有一个用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.)

是否已经有类似的系统存在,或者是否有项目实现了类似的方案,我应该看看想法/灵感?


当前回答

作为插件系统的另一种方法,你可以检查Extend Me项目。

例如,让我们定义一个简单的类及其扩展

# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
    my_attr_1 = 25
    def my_method1(self, arg1):
        print('Hello, %s' % arg1)

# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
    def my_method1(self, arg1):
        super(MyCoolClassExtension1, self).my_method1(arg1.upper())

    def my_method2(self, arg1):
        print("Good by, %s" % arg1)

试着使用它:

>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World

并展示隐藏在幕后的东西:

>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]

extend_me库通过元类来操作类的创建过程,因此在上面的例子中,当创建MyCoolClass的新实例时,我们得到了一个新类的实例,它是MyCoolClassExtension和MyCoolClass的子类,由于Python的多重继承,它具有两者的功能

为了更好地控制类的创建,在这个库中定义了一些元类:

ExtensibleType—通过子类化允许简单的扩展性 ExtensibleByHashType -类似ExtensibleType,但有能力 构建类的专门版本,允许全局扩展 基类和类的专门版本扩展的

这个库是在OpenERP代理项目中使用的,似乎工作得很好!

对于实际使用的例子,看看OpenERP代理的“field_datetime”扩展:

from ..orm.record import Record
import datetime

class RecordDateTime(Record):
    """ Provides auto conversion of datetime fields from
        string got from server to comparable datetime objects
    """

    def _get_field(self, ftype, name):
        res = super(RecordDateTime, self)._get_field(ftype, name)
        if res and ftype == 'date':
            return datetime.datetime.strptime(res, '%Y-%m-%d').date()
        elif res and ftype == 'datetime':
            return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
        return res

这里记录的是可执行对象。RecordDateTime是扩展名。

要启用扩展,只需导入包含扩展类的模块,并且(在上述情况下)在它之后创建的所有Record对象将在基类中具有扩展类,从而具有其所有功能。

这个库的主要优点是,操作可扩展对象的代码不需要了解扩展,而扩展可以改变可扩展对象中的所有内容。

其他回答

在我们当前的医疗保健产品中,我们有一个用接口类实现的插件体系结构。我们的技术栈是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:

现在插件开发团队与核心开发团队是分开的。插件开发的范围是与第三方应用程序集成,这些应用程序定义在产品的任何类别中。插件接口分类如下:-传真,电话,电子邮件…等插件管理器可以增强到新的类别。

在你的情况下:也许你可以写一个插件,然后重复使用它来做一些事情。

如果插件开发人员需要使用重用核心对象,可以通过在插件管理器中进行抽象级别来使用该对象,以便任何插件都可以继承这些方法。

只是分享一下我们是如何在我们的产品中实现的,希望能给大家一点启发。

setuptools有一个EntryPoint:

入口点是发行版“宣传”Python的一种简单方式 供其他发行版使用的对象(如函数或类)。 可扩展应用程序和框架可以搜索入口点 使用特定的名称或组,或者来自特定的分布 或者从sys。路径,然后检查或加载 可以随意发布对象。

如果你使用pip或virtualenv,这个包总是可用的。

我的是,基本上,一个名为“plugins”的目录,主应用程序可以轮询,然后使用imp.load_module来拾取文件,寻找一个已知的入口点,可能是模块级配置参数,然后从那里开始。我使用文件监控的东西来实现一定程度的动态,其中插件是活跃的,但这是一个很好的拥有。

当然,任何出现的要求都是“我不需要(大而复杂的东西)X;我只是想要一些轻量级的东西”会冒着一次重新实现一个已发现需求的风险。但这并不是说你不能从中获得乐趣。

当我搜索Python Decorators时,发现了一个简单但有用的代码片段。它可能不符合你的需求,但非常鼓舞人心。

Scipy高级Python#插件注册系统

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

用法:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")

看看这个对现有插件框架/库的概述,这是一个很好的起点。我很喜欢yapsy,但这取决于你的用例。