Making PyQt4, PySide and IPython work together
PyQt and PySide are two independent Python libraries allowing access to the Qt framework. PyQt is maintained by the British firm Riverbank Computing, whereas PySide is developed by Qt developers from Nokia. PySide was created by Nokia in 2009 after they "failed to reach an agreement with PyQt developers to change its licensing terms to include LGPL as an alternative license" (quoting Wikipedia). Fortunately, the two APIs are very similar (which is not that surprising given that they are just bindings to the same Qt library).
Developers willing to create a Python project based on Qt do not necessarily need to choose between the two libraries: it is possible to support both as soon as some deprecated features of PyQt are not used. Some details can be found on the Qt website or on the PyQt website.
Here I give some tips about how to support both PySide and PyQt4 in a Python project. In addition, I describe how IPython can be configured to work properly with those libraries: it is indeed possible to interact with Qt widgets from the IPython console. This can be extremely helpful for debugging or even in real-world applications. It is also very interesting when using matplotlib from IPython (the GUI backend then being Qt).
Importing Qt in Python
The python_qt_binding package allows to use either PyQt4 or PySide, depending on which is installed. Priority is given to PyQt4, but it can be changed in the code. I prefer to use PyQt for now, since it seems more stable (especially when used in conjunction with IPython), but that will probably change at some point.
To use it, replace all your PyQt4
or PySide
imports with this package, like:
# from PyQt4 import QtGui, QtCore # old imports
from python_qt_binding import QtGui, QtCore # new imports
The python_qt_binding
package must be importable: the folder should be
in the current directory, or put the path to this folder in the Python path
(e.g. by creating an ASCII .pth
file with the path to python_qt_binding
inside).
PyQt4 API v1 and v2
Two APIs are available in PyQt4, v1 and v2. The first version is on the
deprecation road. Python 3 only supports v2, so does PySide. On Python 2.x,
the v1 is the default API. You can change the API with the following code
which comes from the python_qt_binding
package):
import sip
try:
sip.setapi('QDate', 2)
sip.setapi('QDateTime', 2)
sip.setapi('QString', 2)
sip.setapi('QtextStream', 2)
sip.setapi('Qtime', 2)
sip.setapi('QUrl', 2)
sip.setapi('QVariant', 2)
except ValueError, e:
raise RuntimeError('Could not set API version (%s): did you import PyQt4 directly?' % e)
This code must be called before any PyQt4 import. It can be a problem with
IPython, which automatically imports PyQt4 when Qt GUI event loop integration
is active. A possible solution is to paste the above code
in ~/.ipython/profile_default/ipython_config.py
.
Also, you may want to set the
Qt_API
environment variable
to either pyqt
or pyside
depending on which library you want to use.
See here for detailled instructions on Windows.
Configuring IPython
To enable the Qt GUI event loop integration in IPython, you need to uncomment
the following lines in ~/.ipython/profile_default/ipython_config.py
(this file is
automatically created when you create an IPython profile):
c.TerminalIPythonApp.gui = 'qt'
c.TerminalIPythonApp.pylab = 'qt'
This allows you to open a Qt window in an interactive way, and to access the Qt widget instance from IPython while the window is open. It solves also some slow-down issues in the IPython console when windows have been opened. It also works with matplotlib.
Create a Qt window with IPython
When Qt GUI event loop integration is active, a Qt application is automatically created upon IPython launch, so that:
window = MyQtWindow()
window.show
just works. But this won't work by default in Python (e.g. with
python script.py
) since a Qt application won't have been opened in the first
place. By contrast, using the following code:
app = QtGui.QApplication(sys.argv)
window = MyQtWindow()
window.show
app.exec_()
will work with a standard Python console, but will disable interactive GUI integration in IPython! So in order to have the expected behavior in both cases (interactive IPython, or standard Python interpreter), I use the following code:
def create_window(window_class):
"""Create a Qt window in Python, or interactively in IPython with Qt GUI
event loop integration.
"""
app_created = False
app = QtCore.QCoreApplication.instance()
if app is None:
app = QtGui.QApplication(sys.argv)
app_created = True
app.references = set()
window = window_class()
app.references.add(window)
window.show()
if app_created:
app.exec_()
return window
This function can be used like this:
class MyQtWindow(QtGui.QMainWindow):
# [...] your Qt window code
pass
window = create_window(MyQtWindow)