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

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


当前回答

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

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

其他回答

setuptools有一个EntryPoint:

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

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

虽然这个问题很有趣,但我认为在没有更多细节的情况下很难回答。这是什么类型的应用程序?它有GUI吗?它是命令行工具吗?一套脚本?具有唯一入口点的程序,等等……

鉴于我所掌握的信息不多,我将以非常一般的方式回答。

你有什么办法添加插件?

您可能必须添加一个配置文件,该文件将列出要加载的路径/目录。 另一种说法是“该插件/目录中的任何文件都将被加载”,但它要求用户移动文件是不方便的。 最后一个中间选项是要求所有插件都在同一个插件/文件夹中,然后在配置文件中使用相对路径激活/禁用它们。

在纯代码/设计实践中,您必须清楚地确定您希望用户扩展哪些行为/具体操作。确定将总是被覆盖的公共入口点/一组功能,并确定这些操作中的组。一旦完成了这些,扩展应用程序就很容易了,

使用钩子的例子,灵感来自MediaWiki (PHP,但语言真的重要吗?)

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

另一个例子,灵感来自mercurial。在这里,扩展只向hg命令行可执行文件添加命令,扩展行为。

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

对于这两种方法,您可能需要对扩展使用通用的initialize和finalize。 您可以使用所有扩展都必须实现的公共接口(更适合第二种方法;Mercurial使用一个用于所有扩展的reposetup(ui, repo),或者使用一种带有钩子的钩子类型的方法。安装钩子。

但同样,如果你想要更多有用的答案,你必须缩小你的问题;)

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

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

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

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

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

您可以使用pluginlib。

插件很容易创建,并且可以从其他包、文件路径或入口点加载。

创建一个插件父类,定义所需的方法:

import pluginlib

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

通过继承父类创建插件:

import json

class JSON(Parser):
    _alias_ = 'json'

    def parse(self, string):
        return json.loads(string)

加载插件:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))

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