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

4
据我所知,Qt 提供了两种主要的插件机制:
  1. 扩展 Qt 的插件 "Qt Extensions"
  2. 扩展使用 Qt 开发的应用程序的插件
我对开发我的应用程序的插件(2)感兴趣。
我使用 PySide,但找不到任何关于使用 PySide/PyQt 开发应用程序插件的资源。
按照 C++ Qt 文档的说明,应用程序必须使用 Q_DECLARE_INTERFACE() 宏,插件必须同时使用 Q_INTERFACES()Q_EXPORT_PLUGIN2() 宏,但我不知道它们代表什么代码,也无法将其转换为 Python。还是有其他方法我错过了吗?
更新:
到目前为止,我找到的最接近解决方案的东西是Alex Martelli 对类似问题的回答。虽然看起来它可以工作,但我宁愿使用官方的 Qt 方法以避免任何跨平台问题。
2个回答

5
我认为Qt的插件系统旨在允许人们编写作为二进制文件编译的C ++插件。我甚至不知道是否理论上可能编写Python插件,以使用像那样的C ++二进制接口。
如果您想支持Python插件,最好使用其中一个众多的纯Python插件系统。我编写了一个使用YAPSY加载插件脚本的PySide应用程序。 YAPSY是一个非常简单,紧凑的插件模块。它非常容易直接包含在您的应用程序中,因为它只是一个文件,并且是BSD许可证,所以您可以商业使用。只需在Google上搜索即可。我甚至能够使用py2exe打包我的应用程序,仍然保留从插件目录导入Python源文件插件的功能。

对于YAPSY的建议点赞。我已经阅读了相关资料,并找到了一个使用PyQt的好教程(http://lateral.netmanagers.com.ar/weblog/posts/BB923.html)。 - user280407

1
我支持Roll Your Own方法。
所谓插件,是指:
插件:在运行时加载的模块或包,用于增强或修改主模块的行为。
我认为插件有两个要求:
1. 主模块能否在运行时加载插件? 2. 主模块和插件之间的数据是否可访问?
假设:
开发插件系统高度主观。有许多设计决策需要做出,没有一种真正的方法。通常的约束条件是时间、精力和经验。请注意,必须作出假设,并且实现通常定义术语(例如“插件”或“包”)。请友好地记录这些内容。
此插件实现假定:
  • A plugin is either a Python file or a directory (i.e. "plugin package")

  • A plugin package is a directory with structure:

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

    Note that a Plugin package isn't necessarily a Python package. Whether or not a plugin package is a Python package depends on how you want to handle imports.

  • The plugin name is the same as the plugin entry point's file name, as well as the plugin package directory

  • The QApplication's QMainWindow is the primary source of data to share from the main module

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)

为了简洁起见,以下内容省略了错误处理和已加载模块记录(例如通过将它们存储在字典中)。

2. 共享数据

数据可以在两个方向上进行共享:

  • 主模块能否访问插件模块的数据?
  • 插件模块能否访问主模块的数据?

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

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

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

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

共享QMainWindow实例数据的解决方案是将其设置为单例。 这迫使任何QMainWindow成为用户交互的唯一主窗口。 在Python中创建单例的方法有几种。 最常见的两种方法可能是使用元类或模块。 我在使用模块单例方法时取得了最大的成功。

将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.py入口点已经加载了main_window模块,所以它是主模块使用的主窗口(而不是新实例)。现在可以从插件模块访问主模块的数据。

# 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包等。插件可以像您想要的那样简单(或复杂)。希望这个答案为您提供了丰富的灵感,以便您设计插件系统的外观。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接