PyQt6 vs PySide6

What's the difference between the two Python Qt libraries? ...and what's exactly the same (most of it)
Heads up! You've already completed this tutorial.

There is a new version of Qt (version 6) and with it new versions of PyQt and PySide -- now named PyQt6 & PySide6 respectively. In preparation for the Qt6 editions of my PyQt5 & PySide2 books, I've been looking at the latest versions of the libraries to identify the differences between them and find solutions for writing portable code.

Interested in an in-depth guide? My PySide6 book and the PyQt6 book are available now!

In this short guide, I'll run through why there are two libraries, whether you need to care (spoiler: you don't), what the differences are, and how to work around them. By the end, you should be comfortable re-using code examples from both PyQt6 and PySide6 tutorials to build your apps, regardless of which package you're using yourself.

Background

PyQt6 and PySide6: Why are there two libraries?

PyQt is developed by Phil Thompson of Riverbank Computing Ltd. and has existed for a very long time — supporting versions of Qt going back to 2.x. In 2009 Nokia, which owned the Qt toolkit at the time, wanted to make the Python bindings for Qt available under a more permissive LGPL license.

It's called PySide because "side" is Finnish for "binder".

The two interfaces were essentially equivalent, but over time, the development of PySide lagged behind PyQt. This was particularly noticeable following the release of Qt 5 — the Qt5 version of PyQt (PyQt5) has been available since mid-2016, while the first stable release of PySide was 2 years later.

Over 10,000 developers have bought Create GUI Applications with Python & Qt!
Create GUI Applications with Python & Qt6
Take a look

Downloadable ebook (PDF, ePub) & Complete Source code

Also available from Leanpub and Amazon Paperback

[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

However, the Qt project has recently adopted PySide as the official Qt for Python release, which should ensure its viability going forward. When Qt6 was released, both Python bindings were available shortly after:

PyQt6 PySide6
First stable release Jan 2021 Dec 2020
Developed by Riverbank Computing Ltd. Qt
License GPL or commercial LGPL
Platforms Python 3 Python 3

Which should you use? Well, honestly, it doesn't really matter.

Both packages wrap the same library — Qt6 — and so have 99.9% identical APIs (see below for the few differences). Anything you learn with one library will be easily applied to a project using the other. Also, no matter which one you choose to use, it's worth familiarizing yourself with the other so you can make the best use of all available online resources — using PyQt6 tutorials to build your PySide6 applications, for example, and vice versa.

In this short overview, I'll run through the few notable differences between the two packages and explain how to write code that works seamlessly with both. After reading this, you should be able to take any PyQt6 example online and convert it to work with PySide6.

Licensing

The main notable difference between the two versions is licensing — with PyQt6 being available under a GPL or commercial license, and PySide6 under an LGPL license.

If you are planning to release your software itself under the GPL, or you are developing software that will not be distributed, the GPL requirement of PyQt6 is unlikely to be an issue. However, if you want to distribute your software but not share your source code, you will need to purchase a commercial license from Riverbank for PyQt6 or use PySide6.

Qt itself is available under a Qt Commercial License, GPL 2.0, GPL 3.0 and LGPL 3.0 licenses.

For more information, see my FAQ on the implications of GPL vs LGPL licensing in PyQt/PySide apps.

Namespaces and Enums

One of the major changes introduced for PyQt6 is the need to use fully qualified names for enums and flags. Previously, in both PyQt5 and PySide2, you could make use of shortcuts -- for example, Qt.DecorationRole, Qt.AlignLeft. In PyQt6, these are now Qt.ItemDataRole.DisplayRole and Qt.Alignment.AlignLeft respectively. This change affects all enums and flag groups in Qt. In PySide6, both long and short names remain supported.

The situation is complicated somewhat by the fact that PyQt6 and PySide6 use subtly different naming conventions for flags. In PySide6 (and v5), flags are grouped under flag objects with the "Flag" suffix, for example, Qt.AlignmentFlag -- the align left flag is Qt.AlignmentFlag.AlignLeft. The same flag group in PyQt6 is named just "Qt.Alignment". This means that you can't simply choose long or short form and retain compatibility between PyQt6 & PySide6.

UI files loaders

Another major difference between the two libraries is in their handling of loading .ui files exported from Qt Creator/Designer. PyQt6 provides the uic submodule, which can be used to load UI files directly to produce an object. This feels pretty Pythonic if you ignore the camelCase:

python
import sys
from PyQt6 import QtWidgets, uic

app = QtWidgets.QApplication(sys.argv)

window = uic.loadUi("mainwindow.ui")
window.show()
app.exec()

The equivalent with PySide6 is one line longer, since you need to create a QUILoader object first. Unfortunately, the API of these two interfaces is different too---load() vs .loadUi():

python
import sys
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtUiTools import QUiLoader

loader = QUiLoader()

app = QtWidgets.QApplication(sys.argv)
window = loader.load("mainwindow.ui", None)
window.show()
app.exec()

To load a UI onto an existing object in PyQt6, for example, in your QMainWindow.__init__(), you can call uic.loadUi() passing in self (the existing widget) as the second argument:

python
import sys
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6 import uic

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        uic.loadUi("mainwindow.ui", self)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

The PySide6 loader does not support this — the second parameter to load() is the parent widget of the widget you're creating. This prevents you from adding custom code to the widget's __init__() method, but you can work around this by using a separate function:

python
import sys
from PySide6 import QtWidgets
from PySide6.QtUiTools import QUiLoader

loader = QUiLoader()

def mainwindow_setup(widget):
    widget.setWindowTitle("MainWindow Title")

app = QtWidgets.QApplication(sys.argv)
window = loader.load("mainwindow.ui", None)
mainwindow_setup(window)
window.show()
app.exec()

UI-to-Python conversion

Both libraries provide identical scripts to generate Python importable modules from Qt Designer's .ui files. For PyQt6, the script is named pyuic6 and you can use it as shown below:

bash
$ pyuic6 mainwindow.ui -o MainWindow.py

You can then import the UI_MainWindow object, subclass using multiple inheritance from the base class you're using (e.g. QMainWIndow) and then call self.setupUi(self) to set the UI up:

PyQt/PySide 1:1 Coaching with Martin Fitzpatrick — Get one on one help with your Python GUI projects. Working together with you I'll identify issues and suggest fixes, from bugs and usability to architecture and maintainability.

Book Now 60 mins ($195)

python
import sys
from PyQt6 import QtWidgets
from MainWindow import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setupUi(self)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

For PySide6, the tool is named pyside6-uic:

bash
$ pyside6-uic mainwindow.ui -o MainWindow.py

The subsequent setup is identical:

python
import sys
from PySide6 import QtWidgets
from MainWindow import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setupUi(self)

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

PyQt's UI compiler features an -x flag, which you can use to build an executable demo from a UI file. This isn't available in PySide.

Slots and signals

Defining custom slots and signals uses slightly different syntax between the two libraries. PySide6 provides this interface under the names Signal and Slot, while PyQt6 provides these as pyqtSignal and pyqtSlot, respectively. The behavior of both is identical for defining slots and signals.

The following PyQt6 and PySide6 code snippets are identical:

python
my_custom_signal = pyqtSignal()  # PyQt6
my_custom_signal = Signal()  # PySide6

my_other_signal = pyqtSignal(int)  # PyQt6
my_other_signal = Signal(int)  # PySide6

Or for a slot:

python
@pyqtslot
def custom_slot():
    pass

@Slot
def custom_slot():
    pass

If you want to ensure consistency across PyQt6 and PySide6, you can use the following import pattern for PyQt6 to use the Signal and @Slot style there, too:

python
from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

You could, of course, do the reverse from PySide6.QtCore import Signal as pyqtSignal, Slot as pyqtSlot, although that's a bit confusing.

Mouse events

In PyQt6, QMouseEvent objects no longer have the .pos(), .x(), or .y() shorthand property methods for accessing the position of the event. You must use the .position() property to get a QPoint object and access the .x() or .y() methods on that. The .position() method is also available in PySide6.

Features in PySide6 but not in PyQt6

As of Qt 6, PySide supports two Python __feature__ flags to help make code more Pythonic with snake_case variable names and the ability to assign and access properties directly, rather than using getter and setter functions. The example below shows the impact of these changes on code:

python
table = QTableWidget()
table.setColumnCount(2)

button = QPushButton("Add")
button.setEnabled(False)

layout = QVBoxLayout()
layout.addWidget(table)
layout.addWidget(button)

The same code, but with snake_case and true_property enabled:

python
from __feature__ import snake_case, true_property

table = QTableWidget()
table.column_count = 2

button = QPushButton("Add")
button.enabled = False

layout = QVBoxLayout()
layout.add_widget(table)
layout.add_widget(button)

These feature flags are a nice improvement for code readability. However, since they are not supported in PyQt6, writing portable code becomes more difficult.

Supporting both libraries

You don't need to worry about this if you're writing a standalone app; just use whichever API you prefer.

If you're writing a library, widget, or other tool and you want to be compatible with both PyQt6 and PySide6, then you can do so by adding both sets of imports:

python
import sys

try:
    import PyQt6
except ImportError:
    try:
        import PySide6
    except ImportError:
        raise ImportError("neither PyQt6 nor PySide6 is installed")

if "PyQt6" in sys.modules:
    # PyQt6
    from PyQt6 import QtGui, QtWidgets, QtCore
    from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
else:
    # PySide6
    from PySide6 import QtGui, QtWidgets, QtCore
    from PySide6.QtCore import Signal, Slot

This is the approach used in our custom widgets library, where we support PyQt6 and PySide6 with a single library import. The only caveat is that you must ensure PyQt6 or PySide6 is imported before (as we did in the try block) running this code to ensure the target library is in sys.modules.

That's really it! There's not much more to say — the two libraries really are that similar. However, if you do stumble across any other PyQt6/PySide6 examples or features that you can't easily convert, drop me a note.

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PyQt6 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!

More info Get the book

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

PyQt6 vs PySide6 was written by Martin Fitzpatrick with contributions from Leo Well .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.