Qtその他 - PyQt5とPySide2
概要
Qt5でPythonの開発環境を構築する時、PyQt5とPySide2の2種類が存在する。
ここでは、2種類のいくつかの差異を確認して、両方でシームレスに機能するサンプルコードを作成する手順を記載する。
これらを理解することにより、PyQt5の例からPySide2で動作するようにに変換することができる。
歴史
PyQt5とPySide2の2種類のパッケージが存在する理由を記載する。
PyQtは、Riverbank Computing社のPhilThompsonによって開発されている。
2009年にQtツールキットを所有していたNokiaは、Qt用のPythonバインディングをLGPLライセンス(Qtと同様)で利用できるようにしたいと考えていた。
Riverbank社と合意することができず、彼らはPySideとして独自のバインディングをリリースした。
2種類のインターフェースは最初は同等だったが、PySideの開発は最終的にPyQtに遅れをとっていた。(Qt5のリリース後に特に顕著だった)
PyQt5は2016年半ばから利用可能であったが、PySide2の最初の安定したリリースは2018年からである。
Qt5を使用したPythonの例の多くが、PySide2ではなくPyQt5であるのは、この遅延のせいである。
ただし、QtはPySide2を公式で採用したため、その実行可能性が確保されて、今後の人気が高まるはずである。
※注意
Sideは、フィンランド語でbinderを意味するため、PySideと呼ばれている。
PyQt 5 | PySide 2 | |
---|---|---|
最新安定版 (2021年1月現在) |
5.15 | 5.15 |
最初の安定版 | 2016年4月 | 2018年7月 |
開発者 | Riverbank Computing社 | Qt |
ライセンス形態 | GPL または コマーシャル |
LGPL |
プラットフォーム | Python 3以降 | Python 3以降 Python 2.7(Windows x86、Linux x64、MacOS) |
どちらを使用すべきか
PyQt5とPySide2はQt5をラッピングしているため、同じAPIが99.9%存在する。(いくつかの違いについては、後述のセクションを参照)
同じソースコードをそのまま使用できることが多く、import
文をPyQt5からPySide2に変更するだけである。
一方のライブラリで学習したことは、もう一方のライブラリを使用して簡単に適用できる。
例えば、PyQt5のチュートリアルを使用してPySide2を使用したソフトウェアを構築することができる。
ライセンス形態
PyQt5とPySide2のの主な違いは、ライセンス形態である。
PyQt5は、GPLまたは商用ライセンスの下で利用可能であり、PySide2はLGPLライセンスの下で利用可能である。
ソフトウェアをGPLの下でリリースする場合や配布されないソフトウェアを開発している場合、PyQt5のGPL要件が問題になる可能性はほとんどない。
ただし、ソースコードを配布せずにソフトウェアを配布する場合、Riverbank社からPyQt5の商用ライセンスを購入するか、PySide2を使用する必要がある。
LGPLライセンスでは、PySide2にバンドルされている場合でも、開発するソフトウェアのソースコードを配布する必要はない。
LGPLの対象となるソースコードが利用可能になっていることを確認するだけである。(これは、変更があった場合も含まれる)
通常の使用では変更はなく、既にPySide2 / Qt5の標準のソースコードでカバーされている。
Qt5は、Qt Commercial License、GPL2.0、GPL3.0、LGPL3.0ライセンスの下で利用できる。
UIファイル
PyQt5とPySde2は、どちらもQt CreatorまたはQt Designerからエクスポートされた.uiファイルをロードすることができる。
ただし、わずかな差異が存在するため、以下に詳細を記載する。
PyQt5は、UIファイルを読み込んでオブジェクトを生成するuicモジュールがある。
# PyQt 5
import sys
from PyQt5 import QtWidgets, uic
app = QtWidgets.QApplication(sys.argv)
window = uic.loadUi("mainwindow.ui")
window.show()
app.exec()
PySide2は、UIファイルを読み込むために、QUILoader
クラスのオブジェクトを生成する必要がある。
これらはAPI名と引数が異なる。(load
メソッドとloadUI
メソッド)
# PySide 2
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtUiTools import QUiLoader
app = QtWidgets.QApplication(sys.argv)
loader = QUiLoader()
window = loader.load("mainwindow.ui", None)
window.show()
app.exec_()
PyQt5の画面オブジェクト(QMainWindow.__init__等)にUIファイルを読み込むには、
uic.loadUI
メソッドを使用して、第1引数にUIファイルのパス、第2引数にself(ターゲットとなるウィジェット)を渡す。
# PyQt 5
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import uic
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
uic.loadUi("mainwindow.ui", self)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
PySide2の画面オブジェクトにUIファイルを読み込むには、
load
メソッドを使用して、第1引数には、第2引数に作成しているウィジェットの親ウィジェットを渡す。
これにより、ウィジェットの__init__
ブロックにカスタムコードを追加できなくなるが、別の関数を使用してこれを回避できる。
# PySide 2
import sys
from PySide2 import QtWidgets
from PySide2.QtUiTools import QUiLoader
def mainwindow_setup(w):
w.setTitle("MainWindow Title")
app = QtWidgets.QApplication(sys.argv)
loader = QUiLoader()
window = loader.load("mainwindow.ui", None)
mainwindow_setup(window)
window.show()
app.exec()
UIファイルをPythonファイルに変換する
PyQt5とPySide2は、.uiファイルから.pyファイルへインポート可能モジュールを生成するための同一のスクリプトを提供する。
PyQt5の場合は、pyuic5をインストールして、以下のコマンドを実行する。
pyuic5 mainwindow.ui -o MainWindow.py
次に、MainWindowモジュールからUI_MainWindowクラス(使用している基本クラス(QMainWIndow等)を継承したサブクラス)をインポートして、
self.setupUI(self)
メソッドを使用して、UIを設定する。
# PyQt 5
import sys
from PyQt5 import QtWidgets
from MainWindow import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
PySide2の場合、pyside2-uicをインストールして、以下のコマンドを実行する。
pyside2-uic mainwindow.ui -o MainWindow.py
後の設定は、PyQt5と同様である。
# PySide 2
import sys
from PySide2 import QtWidgets
from MainWindow import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
PyQt5またはPySide2でQtDesignerを使用する方法の詳細は、Qt Creatorのチュートリアルを参照すること。
execメソッドとexec_メソッド
execメソッドはQtで使用されており、QApplicationまたはダイアログのイベントループを開始する。
Python 2.7では、execはキーワードだった経緯があり、変数、関数、メソッド名には使用できなかった。
PyQt4やPySideで実施された解決策は、この競合を回避するために、execの名前をexec_に変更することだった。
Python 3では、execキーワードを削除されており、名前を解放して使用できるようになった。
PyQt5は、Python 3のみを対象としているため回避策を取り除くことができ、execメソッド名はQt自体と同じ名前となっている。
ただし、exec_メソッド名は、下位互換性のために維持されている。
PySide2は、Python 3とPython 2.7の両方で使用できるため、引き続き、exec_メソッドを使用する。
ただし、Linux x64およびMacOSでのみ使用できる。
PyQt5とPySide2の両方をターゲットにしている場合は、exec_メソッドを使用する。
スロットとシグナル
PyQt5とPySide2のカスタムスロットとシグナルの定義では、わずかに差異があるシンタックスが使用される。
PySide2は、SignalとSlotという名前でスロットとシグナルを提供している。
PyQt5は、pyqtSignalとpyqtSlotという名前でスロットとシグナルを提供している。
これらの動作は、同じである。
以下に示すPyQt5とPySide2の例の動作は同じである。
# シグナル
my_custom_signal = pyqtSignal() # PyQt5
my_custom_signal = Signal() # PySide2
my_other_signal = pyqtSignal(int) # PyQt5
my_other_signal = Signal(int) # PySide2
# スロット
@pyqtslot
def my_custom_slot():
# Do Something
@Slot
def my_custom_slot():
# Do Something
PyQt5とPySide2の間で一貫性を確保する場合、以下のように、PyQt5またはPySide2にimportパターンを使用して、Signalおよび@Slotを定義する。
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
# または
from PySide2.QtCore import Signal as pyqtSignal, Slot as pyqtSlot
PyQt5とPySide2の両方のサポート
PyQt5とPySide2の両方と互換性を持たせるライブラリ、ウィジェット、その他のツールを開発している場合、
両方のインポートセットを追加することで簡単に実行できる。
以下の例は、カスタムウィジェットライブラリで使用されているアプローチであり、単一のライブラリインポートでPyQt5とPySide2をサポートしている。
注意点は、sys.modulesにあることを確認するために、sysモジュールをインポートする必要がある。
# xxx.pyファイル
import sys
if 'PyQt5' in sys.modules:
# PyQt5
from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
else:
# PySide2
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCore import Signal, Slot
ただし、上記の方法では、複数のPythonファイルが存在する場合、各Pythonファイルで記述するため煩雑である。
この解決策は、インポートロジックを独自のファイルに移動することである。
例えば、プロジェクトのルートにqt.pyファイルを作成して、
PyQt5とPySide2のいずれかからQtモジュール(QtCore、QtGui、QtWidgets等)をインポートした後、他のPythonファイルからインポートする。
以下に、qt.pyファイルのソースコードを記述する。
他のPyQt5モジュールおよびPySide2モジュール(ブラウザ、マルチメディア等)を使用する場合、if-else
ブロックの両方に追加することを忘れないこと。
# qt.pyファイル(プロジェクトのルート)
import sys
if 'PyQt5' in sys.modules:
# PyQt5
from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
else:
# PySide2
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCore import Signal, Slot
次に、他のPythonファイルからQt5(qt.pyファイル)をインポートする。
# xxx.pyファイル
from .qt import QtGui, QtWidgets, QtCore
その他の方法として、環境変数QT_API
を使用して、それらを切り替える方法がある。
詳細は、次のQtPyセクションを参照すること。
QtPy
QtPyは、PyQtやPySideへの単一のAPIを使用して、ソフトウェアが開発できる抽象化レイヤである。
Qt5(QtGuiモジュールがQtGuiとQtWidgetsに分割されている)を使用して、PyQt5、PyQt4、PySide2、PySideをサポートしている。
PyQt5、PyQt4、PySide2、PySide向けの標準化されたPySide2のようなAPIが提供されている。
PySide2モジュールやPyQt5モジュールの代わりに、qtpyモジュールからQtモジュールをインポートする。
QtPyを使用するには、PyQt5、PyQt4、PySide2、PySideをインストールしている必要がある。
これらのライブラリのいずれかが存在する場合、環境変数QT_APIを設定しない時は、標準でPyQt5が使用される。
環境変数QT_API
は、以下の値を取ることができる。
- pyqt5
- PyQt5を使用する。
- pyqtまたはpyqt4
- PyQt4を使用する。
- pyside2
- PySide2を使用する。
- pyside
- PySideを使用する。
QtPyをインストールするには、以下のコマンドを実行する。
pip3 install qtpy
Qt5以外のもの(PyQt 4やPySide 1等)をターゲットにする場合、QtPyのGitHubを参照すること。
QtPyを使用すると、環境変数QT_API
を使用して、ソフトウェアから読み込むAPIを制御できる。
import os
os.environ['QT_API'] = 'pyside2'
from qtpy import QtGui, QtWidgets, QtCore # imports PySide2.
エラー関連
PySide2プロジェクトを実行する時、以下のような警告が出力される場合がある。
Qt WebEngine seems to be initialized from a plugin. Please set Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute before constructing QGuiApplication.
これは、QApplication
クラスのインスタンスを生成する前に、AA_ShareOpenGLContexts
を設定することで修正できる。
以下の例は、PySide2を使用している場合である。
from PySide2 import QtCore, QtWidgets
# ...略
if __name__ == '__main__':
QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
app = QApplication(sys.argv)
# ...略
また、PySide6を使用することで警告を回避することもできる。