PyQt Overview
PyQt is the Python binding for the Qt toolkit - the cross-platform C++ framework whose widgets, the event loop, and the signal/slot system power applications on Linux, Windows, and macOS from a single codebase. The binding (the PyQt5 series for Qt 5, the PyQt6 for Qt 6) exposes the Qt objects as Python classes with the same architecture: the widget hierarchy, the event loop run by QApplication.exec(), and the signal/slot connections that replace the callback model of the other toolkits. This chapter orients the tutorial: what PyQt is and where it sits among the Python GUI options, the Qt object model, the application-and-event-loop architecture every PyQt program runs on, and the signal/slot system that is Qt's defining mechanism.
What PyQt Is
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
from PyQt5 import QtCore, QtWidgets
# the versions: the Qt the binding wraps, and the binding
print("Qt:", QT_VERSION_STR) # 5.15.x
print("PyQt:", PYQT_VERSION_STR) # 5.15.x
# the PyQt5 module family - the import surface:
# QtCore - the non-GUI core: the QObject, the signals,
# the threads, the timers, the data types
# QtGui - the graphics: the paint, the pen, the brush,
# the font, the icon
# QtWidgets- the widgets: the window, the button, the
# layout, the dialog
# QtSql, QtNetwork, QtWebEngine, QtMultimedia, ... -
# the feature modules (the later chapters)
# the PyQt6 line is the same architecture on Qt 6 (the
# chapter on the migration notes the spelling changes)
PyQt is a binding, not a toolkit: the widgets, the rendering, the event system, and the cross-platform windowing all come from Qt (the C++ framework), and the binding provides the Python object model - the QWidget classes, the QObject signal/slot system, the QApplication event loop. The module family is the import surface: QtCore (the non-GUI core - the QObject, the signals, the threads, the timers, the data types), QtGui (the graphics - the paint, the pen, the brush, the font), QtWidgets (the widgets - the window, the button, the layout, the dialog), and the feature modules (QtSql, QtNetwork, QtWebEngine) that the later chapters use. The practical consequence is the one that defines PyQt's place: a PyQt app is a Qt app, it runs on the three desktops from one codebase, and it carries the full Qt feature set (the SQL, the network, the web engine, the multimedia) that the smaller toolkits do not. PyQt is the option when the app needs the cross-platform and the feature depth, and the signal/slot system is the architecture the whole tutorial builds on.
The Application and the Event Loop
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow,
QAction)
# the three-part shape of EVERY PyQt program:
# 1. the QApplication - the app object, ONE per process:
app = QApplication(sys.argv)
# 2. build the UI (the widget tree) and wire the signals
window = QMainWindow()
window.setWindowTitle("NotePad")
window.resize(640, 480)
action = QAction("Quit", window)
action.setShortcut("Ctrl+Q")
window.addAction(action)
# 3. the signal/slot connection (the behavior), and the loop:
action.triggered.connect(app.quit)
window.show()
sys.exit(app.exec()) # the event loop; returns on quit
# the QApplication is the owner: it owns the event loop,
# the application-wide state (the style, the palette), and
# the single-instance rule (a second QApplication is an
# error - the app object is the process's, built once)
Every PyQt program has the same three-part shape, and recognizing it is the orientation skill. The QApplication(sys.argv) is the application object - one per process, the owner of the event loop and the application-wide state (the style, the palette, the platform plugin), and the second QApplication in a process is an error, which is why it is built once in main before any widget. The UI is the widget tree - the QMainWindow (the framed window with the menu bar and the status bar) or the QWidget (the plain window), the child widgets, the layouts that position them. The behavior is the signal/slot connection - the action.triggered.connect(app.quit), the signal bound to the slot. And the loop is the app.exec() (wrapped in sys.exit for the exit code): it enters the event loop, dispatches the events, and returns when the app quits. The loop owns the process: a PyQt app that has not entered it is a UI that cannot respond, and one that has returned is done - the same architecture the other toolkits share, with the Qt spelling.
Signals and Slots: The Defining Mechanism
from PyQt5.QtWidgets import QApplication, QPushButton
import sys
app = QApplication(sys.argv)
button = QPushButton("Click me")
# the signal/slot: the signal is the named event, the slot
# is the connected callable (a function, a bound method,
# a lambda, a functor)
def on_clicked():
print("the button was clicked")
button.clicked.connect(on_clicked)
# the signal carries its arguments; the slot receives them
# (the slot may take FEWER arguments than the signal carries
# - the extras are dropped)
counter = [0]
def on_toggled(checked):
counter[0] = 1 if checked else 0
# button.toggled.connect(on_toggled) # the checked bool
# the disconnect is the inverse:
# button.clicked.disconnect(on_clicked)
# the system is the QObject's: every widget is a QObject,
# and the signal/slot is the mechanism that makes the widget
# call the code - typed, checkable at connect time (the
# mismatch is a warning), and the bridge for the threads
# (the queued connection, the later chapter)
The signal/slot is Qt's defining mechanism, and it is the behavior model the whole tutorial uses: the signal is the named event the object emits (the clicked, the toggled, the textChanged), and the slot is the connected callable - a function, a bound method, a lambda, or a functor. The connect binds them, and the binding is typed - the signal's arguments must match the slot's (the slot may take fewer, the extras are dropped), and a mismatch is a warning at connect time, which is the check that catches the wiring error before it runs. The disconnect is the inverse. The system is the QObject's - every widget is a QObject, and the signal/slot is what makes the widget call the code, with the two properties that distinguish it from the plain callback: the type check at connect, and the thread bridge (the QueuedConnection that delivers the signal across threads, the mechanism the threading chapter relies on). The signal/slot is the reason Qt scales - the widget does not know who listens, several slots can connect to one signal, and the connection is data (connect/disconnect) rather than code structure.
Where PyQt Sits
PyQt's place is the cross-platform, feature-deep option among the Python GUI stacks: alongside the Tkinter (the standard-library, the basic), the PyGTK/PyGObject line (the Linux desktop's native toolkit), and the web-based approaches. The decision criteria are the support horizon (the Qt is maintained actively, the PyQt5/PyQt6 the binding generations), the cross-platform requirement (the Qt is the one Python toolkit that is equally first-class on the three desktops), and the feature depth (the SQL, the network, the web engine, the multimedia - the modules the later chapters use). The licensing is the one practical constraint - the PyQt5/PyQt6 are available under the GPL and under the commercial Qt license (the PyQt6 also under a BSD-like clause for the specific case), and the chapter on the installation covers the choice. The orientation to hold: PyQt is the Qt app in Python, the signal/slot is the behavior model, the QApplication and the exec are the architecture, and the feature modules are the depth that makes it the choice for the application that needs more than a window and a button.
Summary
PyQt is the Python binding for the Qt toolkit: the widgets, the event system, and the cross-platform windowing come from Qt (the C++ framework), and the binding provides the Python object model over the module family - the QtCore (the QObject, the signals, the threads, the data types), the QtGui (the graphics), the QtWidgets (the widgets), and the feature modules (QtSql, QtNetwork, QtWebEngine). Every program has the three-part shape - the QApplication(sys.argv) (one per process, the owner), the widget tree with its signal/slot wiring, and the app.exec() event loop - and the signal/slot is the defining mechanism: the signal is the named event, the slot is the connected callable, the connect is typed (the mismatch warned at connect), and the system is the QObject's, with the thread bridge the plain callback lacks. PyQt is the cross-platform, feature-deep option - the choice for the app that needs the three desktops from one codebase and the Qt feature set, with the licensing as the one practical constraint the installation chapter covers.