この記事はMaya-Python Advent Calendar 2017 12月4日の記事です。
普段それほどアウトプットもしてないので、今年もいくつか勉強がてら書いていこうと思います。
はじめに
Qtももう5.9までバージョンが進みました。
PythonバインディングはQt4系のPyQt,PySide、Qt5系のPyQt5,PySide2があります。
ちなみに弊社はPySideまたはPySide2で統一していますが、githubのプロジェクトを見渡すとビルド環境の関係からか、PySide2ではなくPyQt5を採用しているケースも見受けられます。
Qt4とQt5ではモジュール構成に変更がありました。
QtGuiから多くのウィジェットクラスが新設されたQtWidgetsに移動になってます。
http://doc.qt.io/qt-5/portingguide.html
Autodesk Mayaは2016以前はPySide、2017以降はPySide2となっています。
コードを書き分けるのは何としても回避せねばなりません。
さて、どうしたものか。
普通に書くとどうなるか
PySide
from PySide import QtCore, QtGui
import myapp.ui.mainwindow
class MainWindow(QtGui.QDialog, myapp.ui.mainwindow.Ui_Form):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
PySide2
from PySide2 import QtCore, QtGui, QtWidgets
import myapp.ui.mainwindow
class MainWindow(QtWidgets.QDialog, myapp.ui.mainwindow.Ui_Form):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
QDialogがQtGuiからQtWidgetsに移動してます。myapp.ui.mainwindowはuiファイルから変換して自動生成したpythonコードです。
PySideのコードでもQtWidgets経由に統一するために以下のようにエイリアスを定義すれば大分ごまかせますが、QtWidgetsに移動になっていないクラスもQtWidgets経由で呼べてしまうので気持ちが悪いです。
from PySide import QtGui as QtWidgets
Qt.pyを使おう
みんな困ってますので、誰かが何かを作っているはず。githubで探してみたらやはりありました。
Qt.py
https://github.com/mottosso/Qt.py
PyPIに上がってるので
python -m pip install qt.py
でインストールできます。virtualenv作って試してみましょう
PySide2がインストールされていればPySide2を使用し、無ければPySideを使用してQtモジュール経由でQtCoreQtGui,QtWidgets等を使用できます。Qt5準拠でクラスを整理整頓して返してくれます。
一時期私が修正したコードが消え去ってたりして、開発の安定性に疑問を感じてたのですが、最近またきれいに整ってきました。今なら強くお勧めできます。
このように書けます。スッキリしました。
from Qt import QtCore, QtGui, QtWidgets
import myapp.ui.mainwindow
class MainWindow(QtWidgets.QDialog, myapp.ui.mainwindow.Ui_Form):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
どーしてもPySide2とPySideで処理を分けなければいけない場合は、__binding__
にモジュール名が入っているのでそれで処理を分けることも可能です。
if qt.__binding__ == u"PySide2":
pass
uiファイルから変換したコード
しかしまだ問題は残っています。
uiファイルはあらかじめuiコンパイラーでPythonコードに変換して使用しています。
ちなみにだいぶ昔からこの手法で開発してるのですが、最近「なんで?」と聞かれたんですが答えられませんでした。
確か、uiファイルを動的に読み込むスタイルだと動作が不安定だった?のと、Pythonコードになっていることで全体の挙動が確認しやすいから?だったと思います。
今の時代はエディターのコード補完の恩恵も絶大ですのでこのスタイルを変えることは無いと思います。
PySideのuiコンパイラーとPySide2のuiコンパイラーは当然ながらそれぞれのモジュール構成のpythonコードを出力します。
PySideのuiコンパイラーで生成したコード
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Wed Jul 05 17:43:52 2017
# by: pyside-uic 0.2.15 running on PySide 1.2.4
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(251, 188)
...以下省略...
PySide2のuiコンパイラーで生成したコード
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Wed Jul 05 17:43:52 2017
# by: pyside2-uic running on PySide2 2.0.0~alpha0
#
# WARNING! All changes made in this file will be lost!
from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(251, 188)
...以下省略...
Qt.pyにはPySideおよびPySide2コードをQt.pyを使用したコードに変換する機能も実験的に実装されていますが、あらゆるケースでうまくいくわけではありませんでした。
python -m Qt --convert ui\mainwindow.py
これは安定まで時間がかかりそうでしたので、早々に諦めました。
力業ですが、PySideとPySide2の両方のコードを自動生成しておくことで解決を図ることにしました。
まとめ
uiからの変換はPySide、PySide2両方で行う。
以下のように無駄にPySide、PySide2というパッケージで分けてます。大文字使ってるのもPEP8に反していますがここは判りやすさを優先して割り切ってます。
myapp/main.py
myapp/ui/__init__.py
myapp/ui/mainwindow.ui
myapp/ui/PySide/__init__.py
myapp/ui/PySide/mainwindow.py
myapp/ui/PySide2/__init__.py
myapp/ui/PySide2/mainwindow.py
自分のコードでは、PySideであれPySide2であれ、Qt.pyを使う。
uiのインポートはImportErrorで読み分ける。try,exceptでの読み分けはとてもジェネリックで判りやすいです。
from Qt import QtCore, QtGui, QtWidget
try
import myapp.ui.PySide2.mainwindow as ui_mainwindow
except ImportError:
import myapp.ui.PySide.mainwindow as ui_mainwindow
class MainWindow(QtWidgets.QDialog, ui_mainwindow.Ui_Form):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
ということで、Qt.pyのおかげで個人的には満足いくコードの共通化ができています。
Qt.pyにもまだまだ改善の余地もあるのでどんどんプルリクエストを送りましょう。