Qtその他 - PyQt5とPySide2

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

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を使用することで警告を回避することもできる。