basysKom Application Development Services

Pitfalls in PySide6
Essential Summary
PySide6 is easy to use and powerful but there’s a few pitfalls to look out for. We’ll investigate a performance issue in a large data model, find out that function calls are expensive, and that some of Qt’s C++ APIs had to be adapted to work in a Python environment.

Last time I have shown you the basics of PySide6 and what you can do with it. My colleague Berthold further demonstrated the use of Matplotlib to visualize data. It’s one of the the key selling points for using Python in your project. As with most things in life, where there is light, there is darkness. Let’s have a look at some of the pitfalls to look out for, particularly when piping lots of data through a PySide6 application.

In addition to a plot to visualize your data, you most likely also need some sort of list or table to choose what data to render in the first place. Qt comes with a powerful set of item view classes that use a model-view architecture to manage data and how it’s presented to the user.

Be careful with the Qt global object!

Let’s have a look at the following snippet from a table model returning some data (of course, somewhat simplified):

def data(self, index, role):
    item = index.internalPointer()
    match role:
        case Qt.DisplayRole:
            return item.text()
        case Qt.DecorationRole:
            return item.icon()
        case Qt.ToolTipRole:
            return item.toolTip() 

Spot anything wrong with this? This method is called a lot, every entry in the model has a dozen of roles (display, decoration, tooltip, foreground color, etc). Now imagine scrolling through a table with 10 columns and maybe 30 entries per screen page. While Qt is clever enough not to ask for cells that are out of the visible area and only fetches data when actually needed (e.g. tooltip role is only queried when you hover the item), you’re still looking at maybe 100 calls for each scrolled line. Still, looks like your typical Qt item model code, doesn’t it?

While investigating a performance problem with a table model, we fired up a profiler and were dismayed by what we found: It wasn’t our custom delegate painting code after all! Instead, python spent a good amount  of time in PyObject_GetAttr. Turns out, the Qt object is huge. It contains all the enum values you could think of. Calling Qt.DisplayRole then has to look up the corresponding value in this giant object. Narrowing it down by using Qt.ItemDataRole.DisplayRole significantly improved the model’s performance. Likewise, in painting code you probably want to use Qt.GlobalColor.transparent instead of Qt.transparent to fill your pixmap. While it doesn’t hurt much in a button handler, it definitely adds up in a function that is called thousands of times. Therefore: always use fully qualified enums with PySide6!

Function calls are expensive

In addition, don’t re-implement native methods if you don’t have to. While it’s tempting to just override lessThan or filterAcceptsRow in your QSortFilterProxyModel or making use of __lt__ in your QListWidget, they are also called frequently. If you can, try to use the built-in sort or filter criteria to keep the logic on the C++ side. In case of custom sorting, your model’s sort() function is a good place to do this since it’s only called when actually changing the sort order.

For example, rather than adjusting the sort role on the fly like this:

def lessThan(self, source_left, source_right):
    if self.sortColumn() == MyModel.Columns.SPECIAL_COLUMN:
        data_left = source_left.data(MyModel.Roles.SPECIAL_ROLE)
        data_right = source_right.data(MyModel.Roles.SPECIAL_ROLE)
        return data_left < data_right
    else:
        return super().lessThan(source_left, source_right) 

Adjust the sort column just once:

def sort(self, column, order=Qt.SortOrder.AscendingOrder):
    self.setSortRole(MyModel.Roles.SPECIAL_ROLE if column == MyModel.Columns.SPECIAL_COLUMN else Qt.ItemDataRole.DisplayRole)
    super().sort(column, order) 

Of course, this is only an option if the sorting algorithm is quite simple. It can still come in handy some day.

API caveats

Finally, there’s a few API concepts in Qt that don’t map well to the Python environment, notably, a function taking a reference and thereby altering an existing object. One such example is QValidator. It has a fixup(QString &input) method that you can override to sanitize the given input. PySide6 addresses this by altering the function signature to instead return a string. This is a common difference between C++ Qt and PySide6. Unfortunately, sometimes this wasn’t done consistently and the aforementioned method wasn’t usable from Python. Luckily, once reported, the bug was fixed within an hour and from Qt 6.8.3 it’s possible to write fully custom input validators in Python!

Another one is QDataStream: In C++ you can stream stuff into it using operator<<. In Python, while possible, it often doesn’t yield the desired result since Python really only knows Int and Float (and Complex) numbers. Instead, use the type-safe readInt16, readInt32, readUInt16, readUInt32, etc methods PySide6 adds.

Conclusion

PySide6 is easy to get started with, both when coming from a Python or C++ background. However, there are a few things to consider, particularly if you have a strong background with Qt in C++. If you merely write down the code you thought up in your head with a different syntax (i.e. without braces and semicolons) you can be in for a little surprise as we have seen above. The overhead of Python as an interpreted language and the translation between it and C++ can add up, too. Nevertheless, this blog post should help you overcome the worst obstacles in your next Qt project with Python!

Picture of Kai Uwe Broulik

Kai Uwe Broulik

Kai Uwe Broulik is Software Architect at basysKom where he designs embedded HMI applications based on Qt with C++ and QML. He also trains customers on how to use Qt efficiently. With his more than ten years of experience in Qt he has successfully deployed Qt applications to a variety of platforms, such as mobile phones, desktop environments, as well as automotive and other embedded devices.

Leave a Reply

Your email address will not be published. Required fields are marked *

More Blogarticles

basysKom Newsletter

We collect only the data you enter in this form (no IP address or information that can be derived from it). The collected data is only used in order to send you our regular newsletters, from which you can unsubscribe at any point using the link at the bottom of each newsletter. We will retain this information until you ask us to delete it permanently. For more information about our privacy policy, read Privacy Policy