据我所知,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方法来避免任何跨平台问题。
答案 0 :(得分:4)
我认为Qt的插件系统旨在让人们编写编译为二进制文件的C ++插件。我甚至不知道理论上是否可以在Python中编写将使用类似C ++二进制接口的插件。
如果你想支持Python插件,最好的办法就是使用众多纯python插件系统中的一个。我编写了一个使用YAPSY加载插件脚本的PySide应用程序。 YAPSY是一个非常简单,紧凑的插件模块。它很容易直接包含在您的应用程序中,因为它是一个单独的文件,并且是BSD许可的,因此您可以在商业上使用它。只需在Google上搜索它。我甚至能够使用py2exe打包我的应用程序,仍然保留从插件目录导入python源文件插件的能力。
答案 1 :(得分:0)
我是 Roll Your Own 方法的粉丝。
通过插件,我的意思是:
我看到插件有两个要求:
假设
开发插件系统是非常主观的。有许多设计决策需要做出,没有一种正确的方法。通常的限制是时间、精力和经验。请注意,必须做出假设并且实现通常定义术语(例如“插件”或“包”)。善待并尽可能地记录下来。
此插件实现假设:
插件可以是 Python 文件或目录(即“插件包”)
插件包是一个具有结构的目录:
plugin_package/
plugin_package.py <-- entry point
other_module.py <-+
some_subdir/ |- other files
icon.png <-+
请注意,插件包不一定是 Python 包。插件包是否是 Python 包取决于您希望如何处理导入。
插件名称与插件入口点的文件名相同,以及插件包目录
QApplication 的 QMainWindow 是主模块共享的主要数据来源
加载插件模块需要两条信息:入口点的路径和插件的名称。这些获取方式可能会有所不同,获取它们通常需要字符串/路径解析。对于以下内容,假设:
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 中)可能是一个好主意。为简洁起见,此处不包括这些详细信息。
数据共享有两个方向:
主模块可以在导入后免费访问插件数据(根据定义)。只需访问插件模块的 __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.py
、main_window.py
和 plugin.py
。 main_window.py
定义并实例化主窗口,main.py
使其成为单例并对其进行初始化,plugin.py
导入并使用该实例。
省略了许多细节,希望能够使主要组件变得明显。理想情况下,Qt 和 Python 文档应该足以填补空白...
还有进一步的考虑,例如如何分发和管理插件。插件可以远程托管、打包为 (zip) 档案、捆绑为适当的 Python 包等。插件可以根据需要简单(或复杂)。希望这个答案为您希望插件系统的外观提供了充足的灵感。