如何开发PySide应用程序插件?

时间:2012-01-22 18:34:14

标签: qt pyqt pyside

据我所知,Qt提供了两种主要的插件机制:

  1. 扩展Qt" Qt Extensions"
  2. 的插件
  3. 扩展使用Qt
  4. 开发的应用程序的插件

    我有兴趣为我的应用程序开发插件(2)。

    我使用PySide,但无法使用PySide / PyQt找到有关开发应用程序插件的任何资源。

    根据C ++ Qt文档,我了解应用程序必须使用 Q_DECLARE_INTERFACE()宏,并且插件必须同时使用 Q_INTERFACES() Q_EXPORT_PLUGIN2 ()宏但我不知道他们代表的代码试图将其翻译成python。还是有其他方式我错过了?

    更新

    到目前为止,我能找到的最接近解决方案的是Alex Martelli's answer to a similar question。虽然看起来它会起作用,但我宁愿使用官方的Qt方法来避免任何跨平台问题。

2 个答案:

答案 0 :(得分:4)

我认为Qt的插件系统旨在让人们编写编译为二进制文件的C ++插件。我甚至不知道理论上是否可以在Python中编写将使用类似C ++二进制接口的插件。

如果你想支持Python插件,最好的办法就是使用众多纯python插件系统中的一个。我编写了一个使用YAPSY加载插件脚本的PySide应用程序。 YAPSY是一个非常简单,紧凑的插件模块。它很容易直接包含在您的应用程序中,因为它是一个单独的文件,并且是BSD许可的,因此您可以在商业上使用它。只需在Google上搜索它。我甚至能够使用py2exe打包我的应用程序,仍然保留从插件目录导入python源文件插件的能力。

答案 1 :(得分:0)

我是 Roll Your Own 方法的粉丝。

通过插件,我的意思是:

  • 插件:在运行时加载的模块或包,用于增强或修改主模块的行为

我看到插件有两个要求:

  1. 主模块能否在运行时加载插件?
  2. 主模块和插件之间是否可以访问数据?

假设

开发插件系统是非常主观的。有许多设计决策需要做出,没有一种正确的方法。通常的限制是时间、精力和经验。请注意,必须做出假设并且实现通常定义术语(例如“插件”或“包”)。善待并尽可能地记录下来。

此插件实现假设:

  • 插件可以是 Python 文件或目录(即“插件包”)

  • 插件包是一个具有结构的目录:

    plugin_package/
        plugin_package.py  <-- entry point
        other_module.py    <-+
        some_subdir/         |- other files
            icon.png       <-+
    

    请注意,插件包不一定是 Python 包。插件包是否是 Python 包取决于您希望如何处理导入。

  • 插件名称与插件入口点的文件名相同,以及插件包目录

  • QApplication 的 QMainWindow 是主模块共享的主要数据来源

1.加载插件模块

加载插件模块需要两条信息:入口点的路径和插件的名称。这些获取方式可能会有所不同,获取它们通常需要字符串/路径解析。对于以下内容,假设:

  • plugin_path 是插件入口点的绝对路径,
  • plugin_name 是插件名称(根据上述假设是模块名称)。

Python 已经多次实现并重新实现了导入机制。撰写本文时使用的导入方法(使用 Python 3.8)是:

import importlib
from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader

loader = SourceFileLoader(plugin_name, plugin_path)
spec = spec_from_loader(plugin_name, loader)
plugin_module = module_from_spec(spec)
spec.loader.exec_module(plugin_module)

# Required for reloading a module
sys.modules[plugin_name] = plugin_module

# # This is how to reload a module
# importlib.reload(plugin_module)

包含错误处理和已加载模块的记录(例如,通过将它们存储在 dict 中)可能是一个好主意。为简洁起见,此处不包括这些详细信息。

2.共享数据

数据共享有两个方向:

  • 主模块可以访问插件模块的数据吗?
  • 插件模块可以访问主模块的数据吗?

主模块可以在导入后免费访问插件数据(根据定义)。只需访问插件模块的 __dict__:

# Accessing plugin data from the main module
some_data = plugin_module.__dict__.get('data')

从插件内部访问主模块的数据是一个棘手的问题。

如上所述,我通常认为 QMainWindow 与最终用户的“应用程序”概念是同义词。它是用户与之交互的主要小部件,因此通常包含大部分数据或可以轻松访问这些数据。挑战在于共享 QMainWindow 实例

共享 QMainWindow 实例数据的解决方案是使其成为单例。这会强制任何 QMainWindow 成为用户与之交互的主窗口。在 Python 中有几种方法可以创建单例。两种最常见的方式可能是使用 metaclasses 或模块。我使用模块单例方法取得了最大的成功。

将 QMainWindow 代码分解为单独的 Python 模块。在模块级别,创建但不初始化 QMainWindow。创建一个模块级实例,以便其他模块可以作为模块属性访问该实例。不要初始化它,因为 init 需要一个 QApplication(并且因为主窗口模块不是应用程序入口点)。

# main_window.py
from PySide2 import QtWidgets

class MyMainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

main_window_instance = MyMainWindow.__new__(MyMainWindow)

为应用程序入口点使用单独的模块,例如 main.py。导入主窗口实例,创建QApplication,初始化主窗口。

# main.py
import sys

# Importing the instance makes it a module singleton
from main_window import main_window_instance
from PySide2 import QtWidgets

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_window_instance.__init__()
    main_window_instance.show()
    sys.exit(app.exec_())

导入主窗口实例使其成为单例。在这里创建 QApplication 并不是绝对必要的(它也是一个单例),但对我来说感觉更干净。

现在,当插件在运行时加载时,它们可以导入 main_window_instance。因为 main_window 模块已经由 main.py 入口点加载,所以它是主模块使用的主窗口(而不是新实例)。现在可以从插件模块访问来自主模块的数据。

# plugin.py

# The plugin can now access the main module's data
from main_window import main_window_instance

main_window_instance.setWindowTitle('Changed from plugin')

评论

最低设置需要三个文件:main.pymain_window.pyplugin.pymain_window.py 定义并实例化主窗口,main.py 使其成为单例并对其进行初始化,plugin.py 导入并使用该实例。

省略了许多细节,希望能够使主要组件变得明显。理想情况下,Qt 和 Python 文档应该足以填补空白...

还有进一步的考虑,例如如何分发和管理插件。插件可以远程托管、打包为 (zip) 档案、捆绑为适当的 Python 包等。插件可以根据需要简单(或复杂)。希望这个答案为您希望插件系统的外观提供了充足的灵感。