| name | pyqt |
| description | [Applies to: **/*.py] Enforce modern, maintainable, and performant PyQt application development standards by leveraging Qt Designer, Model-View architecture, and responsive threading. |
| source | cursor_mdc |
PyQt Best Practices
This guide outlines essential practices for building robust, scalable, and maintainable PyQt applications. Adhere to these rules for consistent, high-quality code.
1. UI Design: Qt Designer & Dynamic Loading
Always design your UI visually with Qt Designer (or Qt Design Studio). Keep the generated .ui files pristine. Load them dynamically at runtime or integrate the pyuic-generated code via inheritance without modification. This separates design from logic and simplifies UI updates.
❌ BAD: Hand-editing pyuic-generated files
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
self.my_custom_button.clicked.connect(self.my_logic_function)
✅ GOOD: Dynamic loading with QUiLoader (PyQt6)
from PyQt6.QtWidgets import QApplication, QWidget
from PyQt6.uic import loadUi
class MyMainWindow(QWidget):
def __init__(self):
super().__init__()
loadUi("mainwindow.ui", self)
self.my_button.clicked.connect(self._on_button_clicked)
def _on_button_clicked(self):
print("Button clicked!")
if __name__ == "__main__":
app = QApplication([])
window = MyMainWindow()
window.show()
app.exec()
✅ GOOD: Single inheritance with pyuic-generated code
from PyQt6 import QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
pass
from PyQt6.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.my_button.clicked.connect(self._on_button_clicked)
def _on_button_clicked(self):
print("Button clicked from inherited UI!")
if __name__ == "__main__":
app = QApplication([])
window = MyMainWindow()
window.show()
app.exec()
2. Component Architecture: Model-View Separation
For any data-driven UI (lists, tables, trees), adopt the Model-View architecture. This decouples data management from UI presentation, leading to cleaner, more scalable code.
❌ BAD: Storing data directly in widgets
from PyQt6.QtWidgets import QListWidget, QListWidgetItem
self.my_list_widget = QListWidget()
self.my_list_widget.addItem("Item 1")
self.my_list_widget.addItem("Item 2")
✅ GOOD: Custom QAbstractListModel for data
from PyQt6.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt6.QtWidgets import QListView, QApplication
class MyListModel(QAbstractListModel):
def __init__(self, data=None):
super().__init__()
self._data = data or []
def data(self, index: QModelIndex, role: int):
if role == Qt.ItemDataRole.DisplayRole:
return self._data[index.row()]
return None
def rowCount(self, parent: QModelIndex):
return len(self._data)
def add_item(self, item: str):
self.beginInsertRows(QModelIndex(), len(self._data), len(self._data))
self._data.append(item)
self.endInsertRows()
3. Asynchronous Operations: Keep UI Responsive
Never block the UI thread with long-running tasks. Use QThreadPool with QRunnable or QThread for background processing. Emit signals from worker threads to update the UI on the main thread.
❌ BAD: Blocking UI thread
import time
from PyQt6.QtWidgets import QPushButton, QLabel
def on_heavy_button_clicked(self):
self.status_label.setText("Processing...")
time.sleep(5)
self.status_label.setText("Done!")
✅ GOOD: Offload to QThreadPool
from PyQt6.QtCore import QRunnable, QThreadPool, pyqtSignal, QObject
from PyQt6.QtWidgets import QPushButton, QLabel, QApplication, QWidget
import time
class WorkerSignals(QObject):
finished = pyqtSignal()
result = pyqtSignal(str)
class Worker(QRunnable):
def __init__(self, task_id):
super().__init__()
self.signals = WorkerSignals()
self.task_id = task_id
def run(self):
time.sleep(2)
self.signals.result.emit(f"Task {self.task_id} completed.")
self.signals.finished.emit()
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.threadpool = QThreadPool()
self.button = QPushButton("Start Heavy Task", self)
self.label = QLabel("Ready", self)
self.button.clicked.connect(self._start_task)
():
.label.setText()
worker = Worker(time.time())
worker.signals.result.connect(.label.setText)
worker.signals.finished.connect(: ())
.threadpool.start(worker)
__name__ == :
app = QApplication([])
window = MyWindow()
window.show()
app.()
4. Communication: Signals & Slots
Leverage Qt's powerful signals and slots mechanism for decoupled communication between UI components and business logic.
❌ BAD: Direct method calls for UI events
self.login_button.clicked.connect(self.login_manager.authenticate)
✅ GOOD: Decoupled signals and slots
from PyQt6.QtCore import pyqtSignal, QObject
class LoginManager(QObject):
login_successful = pyqtSignal(str)
login_failed = pyqtSignal(str)
def authenticate(self, username, password):
if username == "user" and password == "pass":
self.login_successful.emit("Welcome!")
else:
self.login_failed.emit("Invalid credentials.")
5. Code Organization & Naming
Follow Pythonic naming conventions (CamelCase for Qt classes, snake_case for methods/variables). Organize code into logical modules (e.g., ui/, models/, workers/). Use Qt resource files (.qrc) for icons, images, and stylesheets.
❌ BAD: Inconsistent naming, monolithic files
class my_main_window(QtWidgets.QMainWindow):
def __init__(self):
self.MyButton = QtWidgets.QPushButton()
✅ GOOD: Consistent naming, modular structure
from PyQt6.QtWidgets import QApplication
from my_app.ui.main_window import MainWindow
if __name__ == "__main__":
app = QApplication([])
main_window = MainWindow()
main_window.show()
app.exec()
from PyQt6.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self._setup_ui()
self._connect_signals()
def _on_button_clicked(self):
pass
6. Styling & Theming
Use Qt's stylesheet system (CSS-like .qss files) for styling. This allows for easy theming (e.g., dark mode) without modifying Python code.
❌ BAD: Hardcoding styles in Python
self.my_button.setStyleSheet("background-color: red; color: white;")
✅ GOOD: External stylesheets
QPushButton {
background-color:
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
}
QPushButton:hover {
background-color:
}
with open("styles.qss", "r") as f:
_stylesheet = f.read()
app.setStyleSheet(_stylesheet)
7. Type Hints
Always use Python type hints for improved readability, maintainability, and static analysis.
❌ BAD: Untyped code
def process_data(data):
✅ GOOD: Type-hinted code
from typing import List, Tuple
from PyQt6.QtCore import QModelIndex
def process_data(data: List[Tuple[bool, str]]) -> List[str]:
return [item[1] for item in data]
def data(self, index: QModelIndex, role: int) -> str | None: