Evaluating Qt
Published at 13:18 on 14 May 2026
I had high hopes for PySide6, the dominant Python binding to the Qt framework. From what I had read, it was clearly the most popular way to do GUI programming in Python, well-supported by an active dev team, and a little checking up revealed this impression was accurate.
All went well until it came time to do some I/O-and-compute-intensive background work. For some reason, the Qt dev team didn’t stop at GUI development; they went on to develop a whole universe that encompasses more than just graphical user interfaces. As an example, instead of using standard Python thread pools, all documentation on worker threads in Qt focuses on using QThreadPool. I was hoping to find some explanation on why things are done this way, and why doing it the standard Python way was or was not a good idea, but I had no luck. Not willing to run risks, QThreadPool it was.
Thankfully, it wasn’t bad. It’s just a typical thread pool that one submits jobs too. The problem happened when my jobs were done and wanted to return some results for the UI thread to display.
In normal UI frameworks, that is simplicity itself. There is a call to submit code for execution in the front-end event loop, you submit a closure containing the result and how to process it, exit the background job, and you are done.
Qt is not normal. It has these quirky relics of the early/mid 90’s attempts to manage concurrency called signals and slots and it expects you to use them everywhere. That includes worker jobs that need to notify the UI event loop of something.
It turns out that signals and slots are seriously broken by design. They don’t work unless both sender and receiver objects remain active until the signal is received by the slot. Any departure from this requirement and things will randomly fail. Sometimes they fail visibly with an exception (often an oddball one out of left field that doesn’t directly signify the cause of the issue). Sometimes signals will just get silently dropped on the floor.
So, no problem, just keep the sender active. But I’m sending the signal to indicate that I am done! Why should I remain active? My work is done, it is time to exit. It is an onerous requirement, one that I don’t have to bother with in any other framework of which I am aware. In wxPython, Swing, and Avalonia, I just submit the code to do the front-end processing to the event loop and exit. In Qt, I have to wait around for the front end to message me back that it has received my results and I may now exit.
And signals themselves are bizarre and un-Pythonic. You create them by defining class variables of type Signal, but you never use them as such. Instead you create instances of that class and Qt does some backhanded, behind-the-scenes hackery to transmute those class variables into instance variables of type SignalInstance. Just one more sharp edge for the programmer to cut themselves on.
It gets worse. It turns out that this is but a particular instance of Qt in Python being for the most part a thin veneer over a library written in C++. There is little or no communication between how C++ manages memory and how Python manages it. Create a control, add it to a parent control, but don’t store it in a global variable or as an instance variable in a long-lived Python object, and Python might garbage-collect it even though Qt still has a C++ pointer to the same memory region. Qt programmers have to follow all sorts of arbitrary rules to avoid memory management issues.
Use Qt and you will be painfully aware of memory management all the time. This cuts to the prime reason I want to use a language like Python, which features automatic memory management, in the first place! Forcing this sort of experience onto the programmer is positively asinine.
This is not a minor nitpick, as it gets in the way of how I like to program. I like to be dynamic and reactive. Just create UI elements on the fly and display them. The logic that manages and displays them will (or, rather, should) hold references to them, keeping them from being garbage collected, until they get closed or removed. Then they vanish, in memory as well as on screen. The number of elements naturally expands and contracts according to the needs of the user. The only constraint is the amount of hardware memory, and possibly quotas on same imposed by the system. Larger systems with more memory can naturally be taken advantage of, as needed. Simple, logical, effective. And needlessly painful to accomplish in Qt.
And this seems to be an only-in-Qt thing. In wxPython, for example, the dev team thought about this, and explicitly coded their glue to the C++ world to avoid these issues. (It’s not very hard to do.)
Qt seems to be the answer to the question: “I want the worst of both worlds. Is there any way I can program in a language as slow as Python and still have the memory-management headaches of C++ to deal with?”
To hell with Qt.