QtPy
QtPy provides an abstraction layer on top of the various Qt bindings (PyQt5, PyQt6, PySide2, and PySide6), allowing developers to write Python GUI applications that can run with any of these backends without modifying code. It automatically detects and uses the available binding, or can be configured to prefer a specific one. The current version is 2.4.3, with a regular release cadence addressing compatibility and feature enhancements.
Warnings
- gotcha QtPy does not install Qt bindings. You must explicitly install at least one binding (e.g., `PyQt5`, `PyQt6`, `PySide2`, or `PySide6`) for QtPy to function. If no binding is found, `QtBindingsNotFoundError` will be raised.
- breaking When migrating to Qt6 bindings (PyQt6/PySide6), be aware of significant changes in the underlying Qt framework. Modules or classes may have been moved (`QtGui.QScreen` to `QtWidgets.QScreen`) or removed (e.g., `QtWebEngineCore` vs `QtWebEngineWidgets` module structure, `QLibraryInfo.LibraryLocation` renamed to `LibraryPath`). While QtPy attempts to abstract these, some code adjustments might still be necessary.
- gotcha If multiple Qt bindings are installed, QtPy follows a specific detection order (usually PyQt6, PySide6, PyQt5, PySide2). To force a specific binding, set the `QT_API` environment variable (e.g., `QT_API='PySide6'`).
- deprecated When using PySide6, `QSqlDatabase.exec()` raises a `DeprecationWarning` in Qt 6.5+. QtPy 2.4.3 and later ignore this warning by default to reduce noise, but the underlying API usage is still deprecated in PySide6.
- gotcha Prior to QtPy 2.4.3, `isinstance` checks for `QMenu` and `QToolBar` (especially when dealing with wrapped objects or specific inheritance hierarchies) could fail unexpectedly, leading to runtime errors or incorrect behavior.
Install
-
pip install qtpy -
pip install qtpy[pyqt5] # or [pyqt6], [pyside2], [pyside6]
Imports
- QtWidgets
from qtpy import QtWidgets
- QtCore
from qtpy import QtCore
- QtGui
from qtpy import QtGui
Quickstart
import sys
import os
from qtpy import QtWidgets, QtCore
# Optional: force a specific Qt API (e.g., 'PyQt5', 'PyQt6', 'PySide2', 'PySide6')
# os.environ['QT_API'] = 'PyQt5'
# Create the application object
app = QtWidgets.QApplication(sys.argv)
# Create a simple widget
widget = QtWidgets.QWidget()
widget.setWindowTitle('Hello QtPy!')
widget.setGeometry(300, 300, 300, 150)
layout = QtWidgets.QVBoxLayout()
label = QtWidgets.QLabel('Welcome to QtPy!')
button = QtWidgets.QPushButton('Click Me')
layout.addWidget(label)
layout.addWidget(button)
widget.setLayout(layout)
def on_button_click():
label.setText('Button clicked from ' + os.environ.get('QT_API', 'auto-detected') + '!')
button.clicked.connect(on_button_click)
# Show the widget
widget.show()
# Start the event loop
sys.exit(app.exec_())