Skip to content


Using PyQt with QtAgg in Jupyterlab – II – excursion on threads, signals and events

In the first post of this series on PyQt

Using PyQt with QtAgg in Jupyterlab – I – a first simple example

we have studied how to set up a PyQt application in a Jupyterlab notebook. The key to getting a seamless integration was to invoke the QtAgg-backend of Matplotlib. Otherwise we did not need to use any of Matplolib’s functionality. For our first PyQt test application we just used multiple nested Qt-widgets in a QMainWindow to create a simple, but interactive and instructive application in a Qt-window on the desktop.

So, PyQt works well with QtAgg and IPython. We just construct and show a QMainWindow; we need no explicit exec() command. An advantage of using PyQt is that we get moveable, resizable windows on our Linux desktop, outside the browser-bound Jupyterlab environment. Furthermore, PyQt offers a lot of widgets to build a full fledged graphical application interface with Python code.

But our funny PyQt example application still blocked the execution of code in other notebook cells! It just demonstrated why we need background threads when working with Jupyterlab and long running code segments. This would in particular be helpful when working with Machine Learning [ML] algorithms. Would it not be nice to put a task like the training of an ML algorithm into the background? And to redirect the intermediate output after training epochs into a textbox in a desktop window? While we work with other code in the same notebook?

The utilization of background threads is one of the main objectives of this post series. In the end we want to see a PyQt application (also hosting e.g. a Matplotlib figure canvas) which displays data that we created in a ML background job. All controlled from a Jupyterlab Python notebook.

To achieve this goal we need a general strategy how to split work between foreground and background tasks. The left graphics indicates in which direction we will move.

But first we need a toolbox and an overview over possible restrictions regarding Python threads and PyQt GUI widgets. In this post we, therefore, will look at relevant topics like concurrency limitations due to the Python GIL, thread support in Qt, Qt’s approach to inter-thread communication, signals and once again Qt’s main event loop. I will also discuss some obstacles which we have to overcome. All of this will give us sufficient knowledge to understand a concrete pattern for a workload distribution which I will present in the next post.

Level of this post: Advanced. Some general experience with asynchronous programming is helpful. But also beginners have a chance. For a ML-project I myself had to learn on rather short terms how one can handle thread interaction with Qt. In the last section of this post you will find links to comprehensive and helpful articles on the Internet. Regarding signals I especially recommend [1.1]. Regarding threads and the helpful effects of event loops for inter-thread communication I recommend to read [3.1] and [3.2] (!) in particular. Regarding the difference between signals and events I found the discussions in [2.1] helpful.

Read More »Using PyQt with QtAgg in Jupyterlab – II – excursion on threads, signals and events