Porting projects to Qt5 and Python3
We have chosen to go for a compatible sources set, which can work with Qt4 or Qt5, Python2 or Python3. Porting to Qt5 and to Python3 are two independent tasks.
- cortical_surface (must be tested)
- freesurfer (must be tested)
- Neuroimaging toolbox (nit)
- Snapbase (must be tested)
How to setup a build workflow using Qt5 and/or python3
- Your system must have Qt5 / python3 + all modules installed. In casa-distro, Ubuntu-16.04 and ubuntu-18.04 development images are already ready for that.
bv_maker.cfgfile, and change in the
[ build ]section:
cmake_options = -DDESIRED_QT_VERSION=5 -DPYTHON_EXECUTABLE=/usr/bin/python3
- then run bv_maker to configure and build (either in casa-distro or directly depending on your config/setup)
To run programs using python3, either run explicitly python3:
But this is not very convenient, and many python scripts start with the standard shebang:
which would normally call python2 because python2 is what is called via the
python command on many systems up to now.
A simple fix for that is to make a symlink from python3 to python in the
bin directory of the build workflow:
ln -s /usr/bin/python3 <builmd_workflow>/bin/python
Then once the build-workflow is setup (bv_env.sh has been sourced from this build workflow) and it is in the paths, the
python command will switch to python3, and all python scripts will do the same.
Qt4 / Qt5
Don’t import directly PyQt5 or PyQt4. We have a compatibility module: soma.qt_gui.qt_backend which replaces the explicit imports:
from soma.qt_gui.qt_backend import Qt widget = Qt.QWidget() # etc.
qt_backend module supports PyQt4, PyQt5 and PySide transparently, and offers a few functions to help links with matplotlib (see init_matplotlib_backend) or for the few functions which do not have the same API in the different implementations (like QFileDialog.getOpenFileName).
If you need to explicitly initialize the backend to a specific Qt implementation, do it only in main scripts directly called from the user, not in modules/libraries which may be imported.
from soma.qt_gui import qt_backend qt_backend.set_qt_backend('PyQt5') from soma.qt_gui.qt_backend import Qt widget = Qt.QWidget() # etc.
But the best way is not to force this, and rely on the
QT_API environment variable that any user can set:
export QT_API=pyqt5 soma_workflow_gui # will use PyQt5.
QT_API variable is already set by the
bv_env program according to the settings of the build workflow, so you even don’t have to care about it in
- No very deep changes in the API, most code written for Qt4 will work with Qt5.
- One of the most important points however is in Pyqt4/PyQt5 handling of slots: in a slot function, when an exception is raised, in PyQt4 the slot would end silently and the program would go on its normal life, but in PyQt5, an exception would cause the whole program to end with an error. So check / catch exceptions in callbacks
- The QtWebKit module has been deprecated, and removed in Ubuntu 18/Qt4. This needs to explicitly switch between QtWebKit (Qt4) and QtWebEngine (Qt5) instead (with a
- A layout problem seems to exist in QGLWidget with Qt5, which causes sometimes GL widgets to appear outside of their parent widget (in a non-reproducible way). We’re working on replacing it with QOpenGLWidget in Qt5 in Anatomist.
Python2 / python3
official porting doc
Things to check:
from __future__ import print_functionat the beginning of source files
print >> file, "babar"->
- use six
- check what is made from results of
dict.items(): they were lists in pyton2, they are generators (iterators) in python3. Sometimes needs to wrap them into real lists:
list(dict.values())is a list (but there is a double list, copied in python2, hence an overhead).
imports are absolute: relative imports should be prefixed with
import .submodule from .submodule import babar
also possible to use (for python2):
from __future__ import absolute_import
unicodedoes not exist,
stris actually unicode. Sometimes just defining
if sys.version_info >= 3: unicode=stris enough, sometimes not…
- metaclasses are different: use
- some modules have changed name/position in python3.
from six.moves import cPickle(or use the regular
from six.moves import StringIO
with open(file[, encoding='utf-8']) as f: six.exec_(f.read()[, ...]), or use
raise (exc_type, exc_instance, exc_traceback)->
six.reraise(exc_type, exc_instance, exc_traceback)
try: .. ; except Exception, e:->
try: ...; except Exception as e:
- to get the 1st item of a generator:
- comparison / sorting operators: redefine
__cmp__is deprecated and not called anymore, sometimes needs to redefine
[func(x) for x in list], or
(func(x) for x in list)if a generator is OK
[x for x in list if func(x)], or
(x for x in list if func(x))if a generator is OK
0777is a syntax error in python3
- dict ordering / iteration order is different and non-reproducible in python 3.0-3.6
- The real nightmare is strings: str, unicode, bytes, and encoding…
- Encoding also causes problems to open text files.
- Some functions used to return
strand now return
bytes, for instance
file.read()when no encoding is used,
- sharing pickled objects between python 2 and 3 is almost impossible, mainly because of these str/unicode/bytes/encoding problems
int divisions: in python2:
>>> 3 / 2 1
>>> 3 / 2 1.5
3 // 2when possible.
- in python2, there are two types
2L. In python3, all ints are longs, and
longdoes not exist any longer.
2Lis a syntax error.