Python PyQt
Tutorial 58 of 65 · pythondeck.com Python course
PyQt and PySide expose the powerful Qt framework to Python. Suited for professional desktop applications with rich widgets, layouts, signals/slots and a visual designer.
PyQt (and PySide) bind Qt’s mature C++ framework—rich widgets, model/view architecture, styling with QSS, and cross-platform desktop apps that feel native.
Choose Qt when tkinter’s limits show and you need custom controls, docking, or multimedia.
Industrial and scientific desktop tools often standardize on Qt because printing, PDF export, and complex table editing are solved problems.
QApplication — event loop; one per process.
Signals & slots — decoupled communication between widgets.
Layouts — QVBoxLayout, QHBoxLayout, QGridLayout instead of absolute positioning.
Model/View — QAbstractTableModel for large tables without per-cell widgets.
Designer .ui — load with QUiLoader or convert to Python.
Threading — QThread + signals back to GUI thread for workers.
Qt separates GUI thread from workers—never touch widgets from background threads. Resource files (.qrc) bundle icons. Qt6 modernized APIs; check PyQt6 vs PySide6 licensing (GPL/commercial vs LGPL). For plotting, embed pyqtgraph or matplotlib FigureCanvas.
Distribution is heavier than tkinter—ship Qt plugins and platform DLLs; consider legal review of LGPL obligations when linking dynamically.
QSettings persists user preferences per platform registry or config files—document keys when adding new options.
Qt Designer accelerates form layout; review generated code in PRs like any other UI contribution.
Updating QLabel from a worker thread causing intermittent crashes.
Ignoring high-DPI scaling flags on 4K monitors.
Massive .ui files without composition into smaller widgets.
Mixing PyQt5 and PyQt6 APIs in one project during migration.
Use signals for all cross-thread UI updates; keep workers pure Python.
Prefer model/view for tables with thousands of rows.
Centralize styles in QSS files for brand consistency.
Automate GUI tests with pytest-qt for critical flows.
Ship minimal Qt platform plugins with pyinstaller to avoid blank window on clean VMs.
Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.
The program below demonstrates hello pyqt. Read the comments on each line, run the code, then change names or values to see how the output shifts.
# Example: Hello PyQt
# Run in the REPL or save as a .py file and execute with python.
from PyQt6.QtWidgets import QApplication, QLabel
app = QApplication([])
label = QLabel("<h1>Hello PyQt</h1>")
label.show()
app.exec()
This sample walks through window + signal in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.
# Example: Window + signal
# Run in the REPL or save as a .py file and execute with python.
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
app = QApplication([])
w = QWidget(); w.setWindowTitle("Demo")
btn = QPushButton("Click")
btn.clicked.connect(lambda: print("clicked"))
QVBoxLayout(w).addWidget(btn)
w.show(); app.exec()
Here is a hands-on illustration of form layout. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.
# Example: Form layout
# Run in the REPL or save as a .py file and execute with python.
from PyQt6.QtWidgets import (QApplication, QWidget, QFormLayout,
QLineEdit, QSpinBox)
app = QApplication([])
w = QWidget(); form = QFormLayout(w)
form.addRow("Name:", QLineEdit())
form.addRow("Age:", QSpinBox())
w.show(); app.exec()
The program below demonstrates signals slots. Read the comments on each line, run the code, then change names or values to see how the output shifts.
# PyQt connects signals to slots for event handling
from PyQt6.QtWidgets import QApplication, QPushButton # widgets
from PyQt6.QtCore import QObject, pyqtSignal # signals
class Bus(QObject): # signal emitter
ping = pyqtSignal(str) # typed signal
def emit_ping(self): # helper
self.ping.emit("hello") # fire signal
app = QApplication([]) # Qt needs QApplication
bus = Bus() # emitter
btn = QPushButton("Click") # UI button
bus.ping.connect(lambda msg: btn.setText(msg)) # slot updates label
btn.clicked.connect(bus.emit_ping) # click triggers signal
btn.show() # display (skip app.exec() in headless runs)
print(btn.text()) # initial text
This sample walks through vbox layout in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.
# Layout managers position widgets responsively
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout # UI
app = QApplication([]) # app object
win = QWidget() # top-level widget
layout = QVBoxLayout(win) # vertical stack
layout.addWidget(QLabel("Title")) # first row
layout.addWidget(QLabel("Body")) # second row
win.setWindowTitle("Demo") # title bar
win.resize(240, 120) # initial size
print(win.windowTitle()) # verify title
Continue with these focused follow-up lessons on Python PyQt: