PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。配布ライセンスは LGPL で公開されています。最新のバージョンは Qt6 に対応した PySide6(記事執筆時点で 6.4.2)です。
Python で作った GUI アプリケーションで、本体に手を加えずに、外部のスクリプトで機能を追加できるようにしたい場合があります。例えば、アプリケーションからスクリプトを読み込むと、ボタンが追加されて新しい機能を利用できるようになるといった具合にです。
Python の標準ライブラリ importlib パッケージ [1] を利用して、動作確認用のサンプルを作りました。
下記の OS 環境で動作確認をしました。
![]() |
Fedora Linux 37 | (Server Edition) | x86_64 |
python3.11 | python3-3.11.1-3.fc37.x86_64 | ||
PySide6 | 6.4.2 |
プラグインのスクリプトには、QPushButton [2] を継承した Plugin クラスが実装されていて、インスタンスはボタンとして機能することを前提にしました。
下記のサンプル qt_plugin_test.py は、ファイルを選択すると読み込んで、読み込んだファイルから Plugin() クラスのインスタンスを作成して、メインウィンドウにボタンを表示する、というだけのサンプルです。
#!/usr/bin/env python | |
# coding: utf-8 | |
import importlib.util | |
import sys | |
from typing import Any | |
from PySide6.QtGui import QAction | |
from PySide6.QtWidgets import ( | |
QApplication, | |
QFileDialog, | |
QMainWindow, | |
QPushButton, | |
QVBoxLayout, | |
QWidget, | |
) | |
def add_module(module_name: str, file_path: str, class_name: str) -> Any: | |
"""Add module | |
Args: | |
module_name (str): module name to add | |
file_path (str): script path to load as plugin | |
class_name (str): class name in the module to use | |
Returns: | |
Any: plugin instance to add into main application | |
Note: | |
https://docs.python.org/3/library/importlib.html | |
""" | |
spec = importlib.util.spec_from_file_location(module_name, file_path) | |
module = importlib.util.module_from_spec(spec) | |
sys.modules[module_name] = module | |
spec.loader.exec_module(module) | |
# example: obj = module.Plugin() | |
obj = eval('module.%s()' % class_name) | |
print(type(obj)) | |
return obj | |
class Example(QMainWindow): | |
layout: QVBoxLayout = None | |
class_name = 'Plugin' | |
def __init__(self): | |
super().__init__() | |
self.id_plugin = 0 | |
self.init_ui() | |
self.setWindowTitle('Plugin Test') | |
def init_ui(self): | |
base = QWidget() | |
self.setCentralWidget(base) | |
self.layout = QVBoxLayout() | |
base.setLayout(self.layout) | |
self.statusBar() | |
open_file = QAction('Open', self) | |
open_file.setShortcut('Ctrl+O') | |
open_file.setStatusTip('Select plugin script.') | |
open_file.triggered.connect(self.plugin_read_dialog) | |
menubar = self.menuBar() | |
file_menu = menubar.addMenu('&File') | |
file_menu.addAction(open_file) | |
def plugin_read_dialog(self): | |
"""Dialog to read plugin script | |
""" | |
dialog = QFileDialog() | |
if dialog.exec(): | |
filename = dialog.selectedFiles()[0] | |
self.add_plugin_widget(filename) | |
def add_plugin_widget(self, filename: str): | |
"""Add plugin widget from specified filename | |
Args: | |
filename (str): plugin script to add | |
""" | |
plugin_name = 'plugin_%d' % self.id_plugin | |
self.id_plugin += 1 | |
obj = add_module(plugin_name, filename, self.class_name) | |
self.layout.addWidget(obj) | |
def main(): | |
app = QApplication(sys.argv) | |
ex = Example() | |
ex.show() | |
sys.exit(app.exec()) | |
if __name__ == '__main__': | |
main() |
指定ファイルから指定クラスをインスタンス化する処理を使い回せるように、add_module という関数にしました。指定したクラスが存在しなかった場合などのエラー処理は何もしていません。
また、プラグイン・スクリプトのモジュールは、一旦読み込んだらリロードしたり削除することを前提にしていないので、sys.modules の辞書に追加する必要があるのかは疑問に思っていますが、とりあえずモジュール名 (module_name) を一意にして、参考サイト [1] で紹介されているサンプルに倣って登録しています。
動作確認用に読み込むプラグイン・スクリプトとして、QPushButton を継承してボタンに文字列を表示しただけの簡単なサンプル plugin_widget_1.py を用意しました。
from PySide6.QtWidgets import QPushButton | |
class Plugin(QPushButton): | |
def __init__(self): | |
super().__init__() | |
self.setText('Plugin Test 1') |
ファイルダイアログを開いて、上のサンプルを選択すると下記のようにボタンが一つ追加されます。
アプリケーション本体側で API を用意して、API を利用した特定用途のボタンをプラグインとして追加する、といったことが使い方ができます。
参考サイト

にほんブログ村
#オープンソース

0 件のコメント:
コメントを投稿