Documents
Differences Between PyQt4 and PyQt5
At this time we are considering porting to QtPy instead of or in addition to Qt5. The QtPy API is similar to that of PyQt5, but under the hood it supports PyQt4.
Known Issues
- QProgressDialog appears automatically.
- GUIs that use QtWebKitEngine will encounter a segfault on exit
- If you are using custom X11 (xmodmap) keyboard remapping or layouts then Qt may not properly handle shortcut keys. These bugs may be related.
The Complete Qt4/PyQt4 to Qt5/PyQt5 Conversion Process
The conversion from Qt4/PyQt4 to Qt5/PyQt5 should be done in three phases as outlined below. Further details about each phase are described in the rest of this document and in Qt4 to Qt5 Conversion.
Preliminaries
- Clean up your old code. Get rid of dead products and code that are no longer being used. This will prevent you from having to do more work than is necessary.
Phase One: Update Qt4 and PyQt4 Code
- If you are maintaining source compatibility, you will need a way to identify the Qt5 environment. At WDAS we use the DISNEY_QT environment variable. If it's undefined, or set to "qt4", then the environment is qt4-centric. If it's set to "qt5" then the environment is using Qt5.
- Update the Qt header includes in your C++ code. See Update Qt Header Includes for details.
- Replace all instances of from PyQt4.Qt<module> import * in your Python code with explicit imports of only the PyQt classes that are being used; or with imports of the PyQt modules, having each class prefixed by the module name. pylint is helpful for finding missing PyQt classes that need to be explicitly imported.
Update to the new-style PyQt signals and slots (which are optional in PyQt4, but required in PyQt5). See PyQt4 to PyQt4 below for details.
# Do the PyQt4 update (using pyqt4topyqt5.py --nopyqt5).
# Do the Qt4 to Qt4 conversion. See Qt4 to Qt4 Update for details.
Phase Two: Convert to SIP API 2.0 [Optional]
- The major issue here is that all PyQt4 scripts will have to be updated before the switch to SIP API 2.0 can be thrown. In this regard, this phase is similar to the next phase.
- The major difference between the SIP API 1.0 and 2.0 is the removal of QString and QVariant (in addition to the new-style signals and slots).
Phase Three: Convert to Qt5 and PyQt5
# Do the PyQt5 conversion (usig pyqt4topyqt5.py).
# Do the Qt5 conversion (manually). See Qt4 to Qt5 Conversion for details.
Final Steps
Fix up any remaining usages of QString and QStringList (see QString and QStringList below).
$EDITOR `git grep -l QString`
Check for any remaining traces of Qt4 and address them.
git grep -i qt4
PyQt4 to PyQt4 Update
The pyqt4topyqt5.py script has a --nopyqt5 flag. When using this flag only updates that are compatible with PyQt4 are performed (mainly updating to the new-style signals and slots). Using this flag produces an output file or directory with the name <file>_PyQt4.py or <dir>_PyQt4, and a log file named pyqt4_to_pyqt4.log(unless you use the --nolog flag).
This snippet of shell script has been found to be useful in converting an entire product:
set product=<repo-name>
pyqt4topyqt5.py --nolog --nopyqt5 ${product}
cd ${product}_PyQt4/
find . -type f | cpio -pdmv ../${product}
cd ../${product}/
PyQt4 Gotchas
The following is a list of issues that people have run into when updating PyQt4 code. The vast majority of these issues have to do with the new-style signals and slots. See the PyQt4 Reference Guide New-Style Signals and Slots for more detailed information on the new-style signals and slots. See also the PyQt4 Reference Guide Things to be Aware Of for other gotchas.
- The old-style PyQt signals and slots silently ignored many types of errors in calls to the connect, disconnect, and emit methods. This is not true of the new-style signals and slots which are much stricter. If you find your converted program throwing an exception during one of these calls, it may be due to a preexisting bug and not a problem with the conversion.
- Invalid old-style signals and slots syntax inside of the SIGNAL or SLOT argument string, e.g.,
- SIGNAL("currentIndexChanged(const QString &") was missing a closing parenthesis and should have been SIGNAL("currentIndexChanged(const QString &)")
- SIGNAL("updateProgress") was missing arguments and should have been SIGNAL("updateProgress(int)")
Overloaded signals. This is an example of an overloaded signal problem found:
. . .
self.emit(SIGNAL("columnReorderRequested(int,int)"), fromIdx, toIdx)
self.emit(SIGNAL("columnReorderRequested(const char*,int)"), self._columnBeingDragged, toIdx)
The automatic conversion script produced this:
columnReorderRequested = pyqtSignal(int, int)
. . .
self.columnReorderRequested.emit(fromIdx, toIdx)
self.columnReorderRequested.emit(self._columnBeingDragged, toIdx)
But what was really needed for this overloaded signal was this:
columnReorderRequested = pyqtSignal([int, int], [str, int])
. . .
self.columnReorderRequested[int,int].emit(fromIdx, toIdx)
self.columnReorderRequested[str,int].emit(self._columnBeingDragged, toIdx)
- String conversion between a signal and a slot. When a signal that is defined as signal = pyqtSignal(str) is connected to a slot that is defined as def slot(self, text), the type of the text argument received by the slot will be QString and not str. This can be avoided by defining a signal as pyqtSignal("const char*") if desired.
- You should also be aware that pyuic4 generates code that uses old-style connections.
- You might have to delete the signal argument type from the converted code. For example, model.itemChanged[QStandardItem].connect(slot) must be changed to model.itemChanged.connect(slot).
- Conversely, you might have to add a signal argument type to the generated pyqtSignal() if no type was specified in the old-style signal string.
If your signal has the same name as a method of the class in which it is defined, then the signal class member will need to be renamed. For example,
executeCode = pyqtSignal(str)
is changed to:
executeCodeSignal = pyqtSignal(str, name='executeCode')
PyQt4 to PyQt5 Conversion
The pyqt4topyqt5 product consists of a Python script that automatically converts a PyQt4 script to a PyQt5 script. It handles PyQt module renaming, and conversion from old-style signals and slots to new-style signals and slots among other things. It has a number of limitations, but it is being enhancing it to handle more cases as people come across them.
The conversion script can be run as follows:
pyqt4topyqt5.py <file.py>
pyqt4topyqt5.py <dir>
This will produce an output file or directory with the name <file>_PyQt5.py or <dir>_PyQt5. The script will convert all files with a Python file name extension of .py, and all executable files which contain a Python shebang ("#!/usr/bin/env python" or "#!/usr/bin/python"). If the script is given a single file which does not meet these criteria, it is assumed to be a text file containing a list of file names to convert.
This snippet of shell script has been found to be useful in converting an entire product repo:
set product=<repo-name>
pyqt4topyqt5.py --nolog ${product}
cd ${product}_PyQt5/
find . -type f | cpio -pdmv ../${product}
cd ../${product}/
Note: pyqt4topyqt5.py will create a pyqt4_to_pyqt5.log file every time it is run unless you use the --nolog flag. You may want to clean up these files.
Qt/PyQt Modules
Many of the Qt (and hence PyQt) modules have been split into separate modules.
- QtGui: The QtGui module has been split into QtGui, QtWidgets, and QtPrintSupport.
- QtOpenGL: Only the QGLContext, QGLFormat and QGLWidget classes are supported by PyQt5.
- QtWebKit: The QtWebKit module has been split into QtWebKit and QtWebKitWidgets.
- pyqtconfig: PyQt4’s pyqtconfig module is not supported in PyQt5.
Note: pyqt4topyqt5.py handles most these changes automatically. However, if a Python script imports all symbols from a Qt module (e.g., from PyQt4.QtWebKit import *) there is a good chance that something will be missed.
The QString and QStringList classes are no longer supported in PyQt5. PyQt5 uses Python's native string types (unicode in Python 2). All QString instances are automatically converted to and from Python unicode strings in PyQt5. Instances of QStringList are converted to and from Python lists of unicode strings. The following is a list of QString method conversions that need to be done by hand.
- Calls to QString.toAscii() need to be removed or replaced with str().
- Calls to other QString methods need to be replaced with their Python string equivalents:
- string.trimmed() --> string.strip()
- string.startsWith() --> string.startswith()
- string.endsWith() --> string.endswith()
- string.isNull() --> not string
- string.length() --> len(string)
- Calls to QString.arg() need to be replaced with Python string formatting.
- If the conversion script runs across instances of QString that it does not know how to convert, the following will be added to the top of the Python file. You may want to remove this line and convert the references to QString by hand yourself.
- The conversion script does not handle QStringLists. Those must be dealt with manually.
In PyQt5 the implementation of QVariant is different to those of PyQt4. By default the behavior is the same as PyQt4’s v2 API. PyQt4’s v2 API does not expose the QVariant class to Python and automatically converts a QVariant to the actual value. See Support for QVariant.
Calls to QVariant.toString() need to be removed or replaced with unicode().
If you subclassed QThread and reimplemented the run() method as a workaround to a problem in PyQt4.6 (or prior versions), remove it from the subclass. The workaround is no longer needed. See Background thread with QThread in PyQt.
If you use QSettings.setValue with a boolean value
boolValue = False
settings.setValue("foo", boolValue)
AND it writes out to a file, be aware that when you read the value
flag = settings.value("foo")
the value (flag) will be a unicode string, not a boolean value.
Other Differences
- QStyle.standardPixmap is deprecated, and QStyle.standardIcon should be used instead.
Gotchas
The following is a list of issues that people have run into when converting PyQt4 code to PyQt5 code. See also the PyQt5 Reference Guide Things to be Aware Of for more PyQt5 gotchas.
- When converting from PyQt old-style signals and slots to the new style, if any of the signals take arguments of a Qt type, then that Qt type must be imported or the places where that type occurs within signal/slot square brackets must be prefixed with QtCore., QtWidgets., or QtGui..
- There is a Qt config file in ~/.config, called Trolltech.conf. A few people have noticed that this file can become very large (greater than 10 MB) when you go back and forth between Qt4 and Qt5, and Qt performance can degrade as the file becomes larger. If you experience UI performance issues, try moving this file aside.
Limitations
In addition to the limitations mentioned above, the following have also been uncovered:
- PyQt4 code that is inside comments or strings will not be converted. This also applies to code within files of other types like C++ or MEL.