【问题标题】:The "right" way to add python scripting to a non-python application将 python 脚本添加到非 python 应用程序的“正确”方法
【发布时间】:2011-03-23 10:24:53
【问题描述】:

我目前正在为用户添加使用 python 脚本插件扩展我的桌面应用程序 (C++) 功能的功能。

天真的方法很简单。嵌入 python 静态库并遵循散布在网络上的数十个教程中的任意数量,这些教程描述了如何初始化和调用 python 文件,您就大功告成了。

不过……

我正在寻找的更像是 Blender 所做的。 Blender 可以通过 python 脚本完全定制,它需要一个 external python 可执行文件。 (即,python 实际上根本没有嵌入 在 blender 可执行文件中。)因此,自然地,当您编写 blender 脚本时,您可以在 site-packages 目录中包含您已经拥有的任何模块。不建议这样做,因为这会限制脚本的可移植性。

所以,我想知道是否已经有一种方法可以让你的蛋糕也可以吃。我想要一个插件系统,它使用:

  • 嵌入式 python 解释器。

    Blender 方法的缺点是它迫使您在系统上全局安装特定的、可能已过时的 python 版本。拥有一个嵌入式解释器可以让我控制正在使用的 python 版本。

  • 防火墙插件。

    每个插件都有一个virtualenv;允许他们安装他们需要或想要的所有模块,但将它们与其他插件中可能发生的冲突分开。也许zc.buildout 在这里是一个更好的候选人,但是,我再次非常愿意接受建议。对于实现这一目标的最佳方法,我有点不知所措。

  • 尽可能无痛...

    为用户。我愿意加倍努力,只要以上大部分内容对插件编写者尽可能透明。


如果你们中的任何人对这类事情有任何经验,我们将不胜感激。 :)


编辑: 基本上,我想要的简短版本是 virtualenv 的简单性,但没有捆绑的 python 解释器,以及一种以编程方式激活特定“虚拟环境”的方法,例如 zc.buildout 使用 sys.path 操作(@ 987654326@技巧)。

virtualenvzc.buildout 都包含我想要的部分内容,但都不会生成我或插件开发人员可以简单地压缩并发送到另一台计算机的可重定位版本。

简单地操作 .pth 文件,或直接在脚本中操作 sys.path,从我的应用程序中执行,我就成功了。但是当需要编译模块时,例如 PIL 是不够的。

【问题讨论】:

  • 当您提到virtualenv 时,您是否暗示您想让插件作者轻松构建外部模块?毕竟,如果您只是为每个插件提供自己的 sys.path 条目,并在插件加载之前在该插件的解释器中预构建,那么它们很有可能毫无困难地打包大多数纯 Python 模块。
  • 也许我是在寻求月球,但如果有一种方法来捆绑编译的模块,比如 PIL,就像你描述捆绑纯 python 模块的方式一样,那就太好了。跨度>
  • 这里不完全是问题的答案,但我根本不会嵌入 Python。为什么要限制自己使用一种脚本语言?而是使用 COM、DBUS 或其他远程调用协议公开脚本 API,并将 Python 写入该 API。

标签: c++ python scripting plugins desktop-application


【解决方案1】:

实现这一目标的一种有效方法是使用消息传递/通信进程架构,允许您使用 Python 实现目标,但不限于 Python。

------------------------------------
| App  <--> Ext. API <--> Protocol | <--> (Socket) <--> API.py <--> Script
------------------------------------

此图试图显示以下内容:您的应用程序使用消息传递与外部进程(例如 Python)进行通信。这在本地机器上很有效,并且可以移植,因为您可以定义自己的协议。您必须为用户提供的唯一东西是一个 Python 库,该库实现您的自定义 API,并使用用户脚本和应用程序之间的发送-接收通信循环进行通信。

定义应用程序的外部 API

您的应用程序的外部 API 描述了外部进程必须能够与之交互的所有功能。例如,如果您希望您的 Python 脚本能够在您的应用程序中绘制一个红色圆圈,您的外部 API 可能包括 Draw(Object, Color, Position)。

定义一个通信协议

这是外部进程用来通过其外部 API 与您的应用程序通信的协议。流行的选择可能是 XML-RPC、SunRPC、JSON 或您自己的自定义协议和数据格式。此处的选择需要足以满足您的 API。例如,如果您要传输二进制数据,那么 JSON 可能需要 base64 编码,而 SunRPC 假设是二进制通信。

构建应用程序的消息系统

这就像一个无限循环在您的通信协议中接收消息、在您的应用程序中为请求提供服务并通过相同的套接字/通道进行回复一样简单。例如,如果您选择 JSON,那么您将收到一条消息,其中包含执行 Draw(Object, Color, Position) 的指令。执行请求后,您将回复请求。

为 Python(或其他)构建消息传递库

这更简单。同样,这是一个代表库用户(即您的用户编写 Python 脚本)发送和接收消息的循环。这个库唯一必须做的就是为您的应用程序的外部 API 提供一个编程接口,并将请求转换为您的通信协议,所有这些都对您的用户隐藏。

例如,使用 Unix 套接字会非常快。

插件/应用程序集合

发现应用程序插件的一个常见做法是指定一个“众所周知的”目录来放置插件。例如,这可能是:

~/.myapp/plugins

下一步是让您的应用程序在此目录中查找存在的插件。您的应用程序应该具备一些智能,以便能够区分 Python 脚本是和不是您的应用程序的真实脚本。

假设您的通信协议指定每个脚本将通过 StdInput/StdOuput 使用 JSON 进行通信。一种简单有效的方法是在您的协议中指定脚本第一次运行时将 MAGIC_ID 发送到标准输出。也就是说,您的应用程序会读取第一个字节(例如 8 个字节),然后查找将其标识为脚本的特定 64 位值。

此外,您应该在您的外部 API 方法中包含允许您的脚本识别自己的方法。例如,脚本应该能够通过外部 API 通知应用程序,例如 NameDescriptionCapabilitiesExpectations ,主要是通知应用程序它是什么,以及它将做什么。

【讨论】:

  • 这看起来很棒,但我对这种类型的架构有点陌生。我将如何“发现”用户插件?
  • @kurige 添加了应用程序/插件集合建议。
  • 这感觉很复杂。与克服编写这么多层的需要的其他解决方案相比,有什么好处? (除了你可以用几种不同的语言扩展你的应用程序)
  • 谢谢,伙计!是的,这稍微复杂一些,但是能够交替使用几种不同的语言将是一个巨大的福音。最后一个问题:在“为 Python 构建消息库”中,您提到将再次出现另一个消息循环。我不确定我是否理解那一点。我假设 python/lua/whatever 消息传递库只是通过指定协议触发消息的一次性函数的集合。不是循环。单独的消息循环意味着某种守护进程?
  • @Calvin1602:很抱歉,但在相关说明中,我考虑这个的唯一原因是因为我正在使用 Qt,其中包括其他很棒的东西: A) 支持开箱即用的 dbus。而且,B) 完全是reentrant。所以,“硬”的东西已经为我处理好了。我的服务大多是线程安全的,尽管我开始编写它们时并没有考虑这种架构。
【解决方案2】:

如果您真的想对您和您的用户尽可能轻松,请考虑扩展 python 而不是嵌入。

  • 嵌入不允许与其他软件轻松集成——python 脚本只能同时使用一个嵌入 python 的程序。 OTOH 扩展意味着用户可以在 python 运行的任何地方使用您的软件;
  • 要使脚本编写者可以使用东西,您不必初始化解释器。解释器已经为您初始化,节省您的工作量。
  • 您不必创建特殊的内置变量和假模块来注入您的嵌入式解释器。只需给他们一个真正的扩展模块,您就可以在首次导入模块时初始化所有内容。
  • 您可以使用 distutils 分发您的软件
  • virtualenv 等工具可以按原样使用——您或用户不必想出新工具。您的用户还可以使用她选择的 IDE/调试工具/测试框架

嵌入确实对您和您的用户没有任何好处。

【讨论】:

  • 扩展是什么意思?
  • @Calvin1602:我所说的嵌入和扩展是指这里提到的docs.python.org/extending/index.html(第 1-4 章是关于扩展,第 5 章是关于嵌入)
  • 好的。但是:“如果主程序(Python解释器)......”->这似乎意味着它向Python添加了功能,而不是对应用程序的可扩展性。我哪里错了?哪个程序先启动?
  • 是的,我也在这里遗漏了一些东西。如果我有一个 GUI 应用程序,使用“扩展 python”方法,那么事件循环将在 python 脚本中,对吗?如果是这样,那么这不是我想要的。
  • @kurige:事件循环不需要在 python 脚本中。事件循环可以是可从 Python 调用的函数,无需返回。然后它可以调用通过其他 PyObjects 注册的回调。 gtk.main() 来自 PyGTK 和 Tkinter.Tk.mainloop() -- 来自 python 发行版本身! -- 两者都这样做。
【解决方案3】:

我不认为将 Python 嵌入到 Boost.Python 中会出现问题。你会得到你想要的一切:

  • 它将被嵌入,它将是一个解释器(具有足够的访问权限来实现自动完成等)
  • 您可以为每个脚本创建一个新的解释器,并拥有完全独立的 python 环境
  • ...尽可能透明

我的意思是,你仍然需要公开和实现一个 API,但是 1)这是一件好事,2)Blender 也能做到,3)我真的想不出另一种方式来利用你工作...

PS:我对 Python / Boost.Python 几乎没有经验,但我曾广泛使用 Lua / LuaBind,这有点像

【讨论】:

  • 这可能正是我最终要做的,但是即使没有提升,为每个脚本创建一个新的解释器也很容易。上面列出的主要问题是无法将插件的外部模块与其他插件的外部模块屏蔽。
  • 也许您可以为每个插件创建一个目录,在该目录中包含所有插件依赖项?但是,让我们等待更少的 hack-ish 解决方案
猜你喜欢
  • 1970-01-01
  • 2021-12-04
  • 2011-11-05
  • 2016-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多