Qt.py enables you to write software that runs on any of the 4 supported bindings – PySide2, PyQt5, PySide and PyQt4.
Table of contents
Write once, run in any binding.
Qt.py was born in the film and visual effects industry to address the growing need for software capable of running with more than one flavor of the Qt bindings for Python – PySide, PySide2, PyQt4 and PyQt5.
|Support co-existence||Qt.py should not affect other bindings running in same interpreter session.|
|Build for one, run with all||Code written with Qt.py should run on any binding.|
|Explicit is better than implicit||Differences between bindings should be visible to you.|
CONTRIBUTING.md for more details.
- Pro tip: Supports vendoring
Use Qt.py as you would use PySide2.
import sys from Qt import QtWidgets app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Hello World") button.show() app.exec_()
All members of
Qt stem directly from those available via PySide2, along with these additional members.
||Version of this project|
||A string reference to binding currently in use|
||Reference to version of Qt, such as Qt 5.6.1|
||Reference to version of binding, such as PySide 1.2.6|
>>> from Qt import __binding__ >>> __binding__ 'PyQt5'
Qt.py also provides compatibility wrappers for critical functionality that differs across bindings, these can be found in the added
>>> from Qt import QtCompat >>> QtCompat.setSectionResizeMode
These are the publicly facing environment variables that in one way or another affect the way Qt.py is run.
|QT_PREFFERED_BINDING||str||Override order and content of binding to try.|
|QT_VERBOSE||bool||Be a little more chatty about what’s going on with Qt.py|
Members of Qt.py is a subset of PySide2. Which means for a member to be made accessible via Qt.py, it will need to (1) be accessible via PySide2 and (2) each of the other supported bindings. This excludes large portions of the Qt framework, including the newly added QtQml and QtQuick modules but guarantees that anything you develop with Qt.py will work identically on any binding – PySide, PySide2, PyQt4 and PyQt5.
The version of PySide2 used as reference is the one specified on VFX Platform. Currently version is 2.0.0.
Branch binding-specific code
Some bindings offer features not available in others, you can use
__binding__ to capture those.
if "PySide" in __binding__: do_pyside_stuff()
Override preferred choice
If your system has multiple choices where one or more is preferred, you can override the preference and order in which they are tried with this environment variable.
$ set QT_PREFERRED_BINDING=PyQt5 # Windows $ export QT_PREFERRED_BINDING=PyQt5 # Unix/OSX $ python -c "import Qt;print(Qt.__binding__)" PyQt5
Constrain available choices and order of discovery by supplying multiple values.
# Try PyQt first and then PySide, but nothing else. $ export QT_PREFERRED_BINDING=PyQt:PySide
Using the OS path separator (
os.pathsep) which is
: on Unix systems and
; on Windows.
Compile Qt Designer files
WARNING – ALPHA FUNCTIONALITY
See #132 for details.
.ui files compiled via
pyside2-uic inherently contain traces of PySide2 – e.g. the line
from PySide2 import QtGui.
In order to use these with Qt.py, or any other binding, one must first erase such traces and replace them with cross-compatible code.
$ pyside2-uic my_ui.ui -o my_ui.py $ python -m Qt --convert my_ui.py # Creating "my_ui_backup.py".. # Successfully converted "my_ui.py"
Now you may use the file as you normally would, with Qt.py
Load Qt Designer files
uic.loadUi function of PyQt4 and PyQt5 as well as the
QtUiTools.QUiLoader().load function of PySide/PySide2 are mapped to a convenience function
import sys from Qt import QtCompat app = QtWidgets.QApplication(sys.argv) ui = QtCompat.load_ui(fname="my.ui") ui.show() app.exec_()
load_ui has only one argument, whereas the PyQt and PySide equivalent has more. See here for details – in a nutshell, those arguments differ between PyQt and PySide in incompatible ways.
sip API v2
If you’re using PyQt4,
sip attempts to set its API to version 2 for the following:
The PyQt and PySide bindings are similar, but not identical. Where there is ambiguity, there must to be a clear direction on which path to take.
The official Qt 5 documentation is always right. Where the documentation lacks answers, PySide2 is right.
# PyQt5 adheres to PySide2 signals and slots PyQt5.Signal = PyQt5.pyqtSignal PyQt5.Slot = PyQt5.pyqtSlot # PySide2 adheres to the official documentation PySide2.QtCore.QStringListModel = PySide2.QtGui.QStringListModel
There are cases where Qt.py is not handling incompatibility issues. Please see
CAVEATS.md for more information.
Send us a pull-request with known problems here!
Who’s using Qt.py?
Send us a pull-request with your studio here.
Presented at Siggraph 2016, BOF!
Projects using Qt.py
Send us a pull-request with your project here.
Projects similar to Qt.py
Also worth mentioning, pyqt4topyqt5; a good starting point for transitioning to Qt.py.
Send us a pull-request with your project here.
Tests are performed on each aspect of the shim.
Each of these are run under..
..once for each binding or under a specific binding only.
Each test is run within it’s own isolated process, so as to allow an
import to occur independently from other tests. Process isolation is handled via nosepipe.
Tests that are written at module level are run four times – once per binding – whereas tests written under a specific if-statement are run only for this particular binding.
if binding("PyQt4"): def test_something_related_to_pyqt4(): pass
Due to the nature of multiple bindings and multiple interpreter support, setting up a development environment in which to properly test your contraptions can be challenging. So here is a guide for how to do just that using Docker.
With Docker setup, here’s what you do.
# Build image cd Qt.py docker build -t mottosso/qt.py27 -f Dockerfile-py2.7 . docker build -t mottosso/qt.py35 -f Dockerfile-py3.5 . # Run nosetests (Linux/OSX) docker run --rm -v $(pwd):/Qt.py mottosso/qt.py27 docker run --rm -v $(pwd):/Qt.py mottosso/qt.py35 # Run nosetests (Windows) docker run --rm -v %CD%:/Qt.py mottosso/qt.py27 docker run --rm -v %CD%:/Qt.py mottosso/qt.py35 # Doctest: test_caveats.test_1_qtgui_qabstractitemmodel_createindex ... ok # Doctest: test_caveats.test_2_qtgui_qabstractitemmodel_createindex ... ok # Doctest: test_caveats.test_3_qtcore_qitemselection ... ok # ... # # ---------------------------------------------------------------------- # Ran 21 tests in 7.799s # # OK
Now both you and Travis are operating on the same assumptions which means that when the tests pass on your machine, they pass on Travis. And everybody wins!
CONTRIBUTING.md for more of the good stuff.