【问题标题】:How to develop a PySide application plugin?如何开发 PySide 应用程序插件?
【发布时间】:2012-01-22 18:34:13
【问题描述】:

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

  1. 扩展 Qt“Qt Extensions”的插件
  2. 扩展使用 Qt 开发的应用程序的插件

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

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

按照 C++ Qt 文档,我了解到应用程序必须使用 Q_DECLARE_INTERFACE() 宏,并且插件必须同时使用 Q_INTERFACES()Q_EXPORT_PLUGIN2 () 宏,但我不知道它们代表的代码尝试将其转换为 python。还是我错过了另一种方式?

更新:

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

【问题讨论】:

    标签: qt pyqt pyside


    【解决方案1】:

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

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

    【讨论】:

    【解决方案2】:

    我是 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)
    

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

    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 包等。插件可以像您想要的一样简单(或复杂)。希望这个答案为您希望插件系统的外观提供充足的灵感。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-29
      • 1970-01-01
      • 1970-01-01
      • 2016-08-20
      • 1970-01-01
      • 2011-11-11
      • 1970-01-01
      • 2010-11-21
      相关资源
      最近更新 更多