qt-bindings

SKILL.md

Python Qt Bindings

Choosing a Binding

Criteria PySide6 PyQt6
Maintainer Qt Company (official) Riverbank Computing
License LGPL v3 GPL v3 / commercial
Commercial use Free (LGPL) Requires commercial license
QML/Qt Quick support Excellent Good
Type stubs Built-in PyQt6-stubs (third-party)
pyqtSignal / Signal Signal pySignal
pyqtSlot / Slot Slot pyqtSlot
Availability pip pip

Default recommendation: PySide6 — official binding, LGPL, ships with complete type stubs, better QML tooling.

API Compatibility Layer

For code that must support both:

try:
    from PySide6.QtWidgets import QApplication, QPushButton
    from PySide6.QtCore import Signal, Slot
    PYSIDE6 = True
except ImportError:
    from PyQt6.QtWidgets import QApplication, QPushButton
    from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
    PYSIDE6 = False

Or use qtpy — an abstraction layer maintained by the community:

from qtpy.QtWidgets import QApplication, QPushButton
from qtpy.QtCore import Signal, Slot
# Works with PySide6, PyQt6, PySide2, PyQt5 — set QT_API env var to select

PySide6 vs PyQt6: Key Differences

Signals and Slots

# PySide6
from PySide6.QtCore import Signal, Slot
class Foo(QObject):
    my_signal = Signal(int)

    @Slot(int)
    def my_slot(self, value: int): ...

# PyQt6
from PyQt6.QtCore import pyqtSignal, pyqtSlot
class Foo(QObject):
    my_signal = pyqtSignal(int)

    @pyqtSlot(int)
    def my_slot(self, value: int): ...

Enum Access

Both require fully-qualified enum access (breaking change from Qt5):

# CORRECT (both bindings)
Qt.AlignmentFlag.AlignLeft
QSizePolicy.Policy.Expanding
QPushButton.setCheckable(True)

# WRONG — Qt5 style (no longer works)
Qt.AlignLeft

Exec Method (PyQt6 breaking change)

# PySide6
app.exec()
dialog.exec()

# PyQt6 — exec() also works in PyQt6 (exec_ removed)
app.exec()
dialog.exec()

Both use exec() — the old exec_() workaround is no longer needed or available in PyQt6.

Property Decorator

# PySide6
from PySide6.QtCore import Property
@Property(int, notify=value_changed)
def value(self) -> int: return self._value

# PyQt6
from PyQt6.QtCore import pyqtProperty
@pyqtProperty(int, notify=value_changed)
def value(self) -> int: return self._value

Migrating PyQt5 → PySide6

Step 1: Update imports

# Mass replace with sed
sed -i 's/from PyQt5\./from PySide6./g' src/**/*.py
sed -i 's/import PyQt5\./import PySide6./g' src/**/*.py

Step 2: Replace signal/slot decorators

# PyQt5 → PySide6
pyqtSignal → Signal
pyqtSlot  → Slot
pyqtProperty → Property

Step 3: Fix enum usage (most common PyQt5→PySide6 breakage)

# PyQt5 (short form)
Qt.AlignLeft          → Qt.AlignmentFlag.AlignLeft
Qt.Horizontal         → Qt.Orientation.Horizontal
QSizePolicy.Expanding → QSizePolicy.Policy.Expanding
Qt.WindowModal        → Qt.WindowModality.WindowModal

Step 4: Fix exec() calls — remove exec_() suffix:

app.exec_()   → app.exec()
dialog.exec_() → dialog.exec()

Step 5: Remove deprecated Qt5 API

# Removed in Qt6
QWidget.show() — still works
QApplication.setDesktopSettingsAware() — removed
QFontDatabase.addApplicationFont() — still works

Migrating PyQt5 → PyQt6

Same steps as PySide6 migration, but:

# PyQt5 → PyQt6 signals (keep pyqt prefix)
pyqtSignal → pyqtSignal  (unchanged)
pyqtSlot   → pyqtSlot    (unchanged)

# Imports change
from PyQt5.QtWidgets import ...from PyQt6.QtWidgets import ...

Enum changes are identical to PySide6 — both Qt6 bindings enforce fully-qualified enums.

Migrating PySide2 → PySide6

# Imports
from PySide2.from PySide6.

# exec_ removal
.exec_().exec()

# Enum qualification (same as PyQt5→PySide6)

PySide6 also drops Python 3.6/3.7 support — minimum is Python 3.8 (3.11 recommended).

Type Stubs

# PySide6 ships stubs — no extra install
pip install PySide6

# PyQt6
pip install PyQt6-stubs

# Configure pyright/mypy
# pyproject.toml
[tool.pyright]
pythonVersion = "3.11"
Weekly Installs
8
GitHub Stars
1
First Seen
Feb 27, 2026
Installed on
opencode8
gemini-cli8
antigravity8
claude-code8
github-copilot8
codex8