| name | pyside |
| description | [Applies to: **/*.py] Definitive guide for building modern, maintainable, and performant PySide6 applications using best practices like UI/logic separation, modern controls, and robust type safety. |
| source | cursor_mdc |
PySide6 Best Practices
This guide outlines the essential best practices for developing robust, maintainable, and modern PySide6 applications. Adhere to these principles to ensure high-quality, performant, and future-proof code.
1. Code Organization & UI Generation
Principle: Strictly separate UI definition from application logic. Leverage Qt Designer for visual UI creation and pyside6-uic for generating Python UI classes.
Rule: Always design your user interfaces visually in Qt Designer. Convert the .ui files to Python classes using pyside6-uic, then import and compose these generated UI classes within a dedicated Python controller class. Never manually modify the generated ui_*.py files.
❌ BAD: Hand-coding complex UI layouts directly in Python, or modifying generated UI files.
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Bad UI Design - Hand-coded")
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
self.button = QPushButton("Click Me")
layout.addWidget(self.button)
self.button.clicked.connect(self.on_button_clicked)
def on_button_clicked(self):
print("Button clicked!")
✅ GOOD: Use pyside6-uic generated UI classes composed in a controller.
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_my_app import Ui_MainWindow
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setWindowTitle()
.ui.myButton.clicked.connect(._on_button_clicked)
():
()
__name__ == :
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.())
2. Modern Controls & QML
Principle: Embrace modern UI/UX with Qt Quick Controls 2 for fluid, material-style components. Use QML for declarative UI, and Python for business logic.
Rule: Prefer Qt Quick Controls 2 for new UIs. Only create custom QML controls when built-in options are insufficient. Keep QML focused on UI declaration; expose data and complex logic from Python via QObject properties and slots.
❌ BAD: Mixing complex business logic directly into QML, or using QWidgets for highly dynamic/animated interfaces where QML excels.
// main.qml (Bad: Complex logic in QML)
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 640; height: 480; visible: true
title: "Bad QML - Logic in UI"
TextField { id: inputField; text: "10" }
Button {
text: "Calculate Factorial"
onClicked: {
// Bad: Complex calculation directly in QML
let n = parseInt(inputField.text);
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
resultLabel.text = "Factorial: " + result;
}
}
Label { id: resultLabel; text: "Result: " }
}
✅ GOOD: QML for UI, Python for logic.
from PySide6.QtCore import QObject, Property, Signal, Slot
import math
class Backend(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._input_value = "10"
self._result_value = ""
inputValueChanged = Signal()
resultValueChanged = Signal()
@Property(str, notify=inputValueChanged)
def inputValue(self) -> str:
return self._input_value
@inputValue.setter
def inputValue(self, value: str):
if self._input_value != value:
self._input_value = value
self.inputValueChanged.emit()
@Property(str, notify=resultValueChanged)
def resultValue(self) -> str:
return self._result_value
@resultValue.setter
def resultValue(self, value: ):
._result_value != value:
._result_value = value
.resultValueChanged.emit()
() -> :
:
n = (.inputValue)
n < :
.resultValue =
:
.resultValue =
ValueError:
.resultValue =
PySide6.QtGui QGuiApplication
PySide6.QtQml QQmlApplicationEngine
sys
backend Backend
__name__ == :
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
backend = Backend()
engine.rootContext().setContextProperty(, backend)
engine.load()
engine.rootObjects():
sys.exit(-)
sys.exit(app.())
QtQuick
QtQuick.Controls
ApplicationWindow {
width: ; height: ; visible: true
title:
ColumnLayout {
anchors.fill: parent
anchors.margins:
TextField {
Layout.fillWidth: true
placeholderText:
text: backend.inputValue // Bind to Python
onTextChanged: backend.inputValue = text
}
Button {
Layout.fillWidth: true
text:
onClicked: backend.calculateFactorial() // Call Python slot
}
Label {
Layout.fillWidth: true
text: backend.resultValue // Bind to Python
}
}
}
3. Type Safety & Linting
Principle: Leverage PySide6's robust type hints for early error detection and improved code readability.
Rule: Always use type hints for all PySide6 properties, signals, slots, and method parameters. Enforce PEP 8 naming conventions (snake_case for Python functions/variables, CamelCase for Qt classes/methods) using black for formatting and mypy/pylint for static analysis in CI.
❌ BAD: Untyped code, inconsistent naming.
class MyWidget(QMainWindow):
def __init__(self):
super().__init__()
self.my_button = QPushButton("Click")
self.my_button.clicked.connect(self.handle_click)
def handle_click(self):
print("Clicked")
def AnotherFunction(arg):
pass
✅ GOOD: Fully typed, PEP 8 compliant, clear intent.
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget
from PySide6.QtCore import Slot
class MyWidget(QMainWindow):
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.my_button: QPushButton = QPushButton("Click Me")
self.setCentralWidget(self.my_button)
self.my_button.clicked.connect(self._handle_click)
@Slot()
def _handle_click(self) -> None:
"""Handles the button click event."""
print("Button was clicked!")
def another_helper_function(data: str) -> bool:
"""A helper function with type hints."""
return len(data) > 0
4. Styling & Theming
Principle: Achieve a consistent, modern UI aesthetic across your application.
Rule: Use QtVSCodeStyle for applying modern, VS Code-inspired themes. Always set the Qt.ApplicationAttribute.AA_UseHighDpiPixmaps attribute for crisp SVG icons on high-DPI displays.
❌ BAD: Default Qt styling, blurry icons on high-DPI screens.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
app = QApplication(sys.argv)
main_win = QMainWindow()
push_button = QPushButton("Unstyled Button")
main_win.setCentralWidget(push_button)
main_win.show()
sys.exit(app.exec())
✅ GOOD: Consistent theme, high-DPI support.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
from PySide6.QtCore import Qt
import qtvscodestyle as qtvsc
app = QApplication(sys.argv)
app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
stylesheet = qtvsc.load_stylesheet(qtvsc.Theme.DARK_VS)
app.setStyleSheet(stylesheet)
main_win = QMainWindow()
push_button = QPushButton("Styled Button")
main_win.setCentralWidget(push_button)
main_win.show()
sys.exit(app.exec())
5. Signal/Slot Hygiene
Principle: Maintain clean, readable, and robust signal/slot connections.
Rule: Use the modern signal.connect(slot) syntax. Avoid lambda-heavy connections inside loops; use functools.partial or dedicated methods for passing arguments. Encapsulate complex slot logic in separate, well-named methods.
❌ BAD: Old-style connections, lambdas in loops leading to closure issues.
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
for i in range(5):
button = QPushButton(f"Button {i}")
layout.addWidget(button)
✅ GOOD: New-style connections, proper argument handling with functools.partial.
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
from PySide6.QtCore import Slot
from functools import partial
import sys
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
for i in range(5):
button = QPushButton(f"Button {i}")
layout.addWidget(button)
button.clicked.connect(partial(self._handle_button_with_arg, i))
self.another_button = QPushButton("Simple Action")
layout.addWidget(self.another_button)
self.another_button.clicked.connect(self._handle_simple_action)
@Slot(int)
def _handle_button_with_arg(self, index: int) -> None:
print(f"Button {index} clicked!")
@Slot()
() -> :
()
._perform_complex_sub_action()
() -> :
()
__name__ == :
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.())
6. Performance & Concurrency
Principle: Maintain a responsive UI by offloading long-running tasks from the main thread.
Rule: Never perform blocking I/O (e.g., network requests, file operations) or