PySide6 Code Reviewer
Expert code review for modern PySide6/Qt 6.8+ applications.
Review Process
- •Identify Qt version assumptions — Verify code targets Qt 6.8+ (no Qt5 compat)
- •Check thread safety — All GUI operations on main thread, proper worker patterns
- •Validate signal/slot usage — Modern connection syntax, proper signatures
- •Assess Model/View implementation — Role usage, data method patterns, index validity
- •Review resource management — Parent-child ownership, prevent leaks
- •Evaluate async patterns — QThread, QtConcurrent, asyncio integration
- •Check QML integration — Property bindings, type registration, context exposure
Critical Anti-Patterns (Always Flag)
python
# WRONG: GUI operation from worker thread
class Worker(QThread):
def run(self):
self.label.setText("Done") # CRASH: Cross-thread GUI access
# WRONG: Blocking the event loop
def on_button_click(self):
time.sleep(5) # FREEZES UI
requests.get(url) # FREEZES UI
# WRONG: Old-style signal connection (Qt4/5 legacy)
self.connect(button, SIGNAL("clicked()"), self.handler)
# WRONG: String-based slot connection
button.clicked.connect("self.handler") # Should be callable
# WRONG: No parent = memory leak risk
label = QLabel("text") # Should have parent or be assigned to layout
# WRONG: Deleting QObject while signals pending
obj.deleteLater() # OK
del obj # WRONG if signals/slots active
# WRONG: Direct widget manipulation in QThread.run()
class BadWorker(QThread):
def run(self):
self.progress_bar.setValue(50) # Thread violation!
Modern Patterns (Require These)
Signal/Slot Connections
python
# Qt 6 style - always use this
button.clicked.connect(self.on_click)
button.clicked.connect(lambda: self.handler(arg))
# Typed signals with modern syntax
class Worker(QObject):
progress = Signal(int) # Single type
result = Signal(str, list) # Multiple types
error = Signal(Exception) # Exception passing
finished = Signal() # No arguments
Thread-Safe Worker Pattern
python
class Worker(QObject):
finished = Signal()
progress = Signal(int)
result = Signal(object)
error = Signal(str)
@Slot()
def run(self):
try:
for i in range(100):
# Do work
self.progress.emit(i)
self.result.emit(data)
except Exception as e:
self.error.emit(str(e))
finally:
self.finished.emit()
# Usage
thread = QThread()
worker = Worker()
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.start()
Async Integration (Qt 6.8+)
python
import asyncio
from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
# Set up asyncio with Qt event loop
asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy())
class AsyncWidget(QWidget):
async def fetch_data(self):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
def start_fetch(self):
asyncio.ensure_future(self.fetch_data())
Detailed Reference Files
- •references/signals-slots.md — Signal/slot patterns, connection types, thread-safe emission
- •references/model-view.md — QAbstractItemModel, roles, proxies, delegates
- •references/threading.md — QThread, QtConcurrent, async patterns, thread pools
- •references/widgets.md — Widget lifecycle, layouts, styling, high-DPI
- •references/qml-integration.md — QML/Python bridge, properties, type registration
- •references/performance.md — Paint optimization, model efficiency, lazy loading
- •references/anti-patterns.md — Comprehensive anti-pattern catalog with fixes
Review Checklist (Use for PRs)
Thread Safety
- • All widget/GUI calls on main thread only
- • Workers use signal/slot for UI updates
- • No
time.sleep()or blocking calls in main thread - •
QThreadsubclass only overridesrun(), not constructor work - •
moveToThread()used correctly (object has no parent)
Memory Management
- • Widgets have parents OR are added to layouts
- •
deleteLater()used for QObjects, neverdel - • Lambda connections don't capture stale references
- • Circular signal connections avoided
- • Model data returned by value, not reference
Signal/Slot Correctness
- • Modern connection syntax (no strings)
- • Signal signatures match slot signatures
- •
@Slot()decorator on all slots - •
Qt.QueuedConnectionfor cross-thread signals - • Signals defined as class attributes
Model/View
- •
beginInsertRows()/endInsertRows()bracket changes - •
dataChangedemitted for updates - •
index.isValid()checked before access - • Custom roles use
Qt.UserRole + n - •
roleNames()override for QML compatibility
Performance
- • No widget creation in paint events
- •
update()notrepaint()for redraws - • Large lists use QAbstractItemModel (not QListWidget)
- • Lazy loading for expensive data
- •
blockSignals()during batch updates
Qt 6.8+ Specifics
- • Using new enum scoping (
Qt.AlignmentFlag.AlignCenter) - •
QtAsynciofor async patterns (not third-party) - •
QML_ELEMENTmacro equivalents for type registration - • Property bindings via
QPropertywhere appropriate - • New
QPermissionAPI for platform permissions