Build a Desktop App with PySide6 #1: What PySide6 Is — Desktop Apps with Qt and Python
Once you have worked through Modern Python Basics, you can build most automation scripts and CLI tools in Python. But the moment you hand a tool to a coworker or a family member, you hit a wall. You cannot tell someone who has never opened a terminal to “activate the virtual environment and run the script.” This is the point where you need a window with a button and an input field, and this series covers how to build that window. It is the desktop extension of the Python track.
It runs in seven parts.
- #1 What PySide6 is — desktop apps with Qt and Python ← this post
- #2 Widgets and layouts — assembling the screen
- #3 Signals and slots — the core of event handling
- #4 Qt Designer and UI files — draw the screen, then load it
- #5 Model/View — connecting data to lists and tables
- #6 Threads and timers — keeping the UI responsive
- #7 Packaging and distribution — building executables with PyInstaller
This post builds the case for choosing PySide6 among the Python GUI options, then covers installation, a first window, and the role of the event loop.
The Python GUI landscape #
There are three broad ways to build a desktop screen in Python.
| Approach | Representative | Characteristics |
|---|---|---|
| Standard library | tkinter | No installation needed. Few widget types and a dated default look |
| Qt bindings | PySide6, PyQt6 | A mature widget set, near-native appearance, a long track record in commercial software |
| Web-technology based | Electron family, pywebview | Reuses a web frontend. In exchange, ships a browser engine, so it is heavy |
For a simple internal tool, tkinter is enough. But as widgets multiply and screens grow, tkinter takes more and more manual work, and the web-based route carries the weight problem of “a notepad-level app that ships at hundreds of MB.” In between sits the option that gives you native-level performance and a rich widget set in plain Python syntax — the Qt bindings — and this series uses PySide6.
PySide6 and PyQt6 — same Qt, different licenses #
Two bindings for using Qt from Python coexist. The names are confusingly similar, but the APIs are nearly identical, and the decisive difference is who makes it and under what license it ships.
| Aspect | PySide6 | PyQt6 |
|---|---|---|
| Maker | Qt Group (official Qt) | Riverbank Computing (third party) |
| License | LGPL v3 (free to ship commercial apps) | GPL v3 or a paid commercial license |
| Official name | Qt for Python | PyQt |
| API differences | Minor notation differences such as Signal, Slot | pyqtSignal, pyqtSlot notation |
The license is the heart of it. With LGPL PySide6 you can build and sell a closed-source commercial app for free, while doing the same with GPL PyQt6 requires buying a commercial license. Searching for learning material turns up more PyQt articles, but the APIs are close enough to cross-reference, and if you are starting fresh, PySide6 — officially supported and license-friendly — is the safe choice.
Installation #
Create a project directory and install PySide6. We use uv, the same tool as in the Modern Python track.
uv init todo-app
cd todo-app
uv add pyside6pip install pyside6The installation pulls in hundreds of Qt modules. The two this series mainly uses are QtWidgets, where the widgets live, and QtCore, which holds core features such as signals and timers.
Showing your first window #
Let’s put a window on screen right away. Twenty lines are enough.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My first PySide6 app")
self.resize(400, 300)
button = QPushButton("Click me")
self.setCentralWidget(button)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())uv run python main.pyIf a window appears with a button filling it, you have succeeded. The code is short, but its structure is the skeleton of this entire series, so let’s confirm it line by line.
QApplication— the object that manages the whole application. Create exactly one per program, before any widget.QMainWindow— the top-level window with a menu bar, status bar, and central widget area. Putting your screen composition into a class that inherits from it is the standard pattern.setCentralWidget— places a widget in the window’s central area. For now it is a single button; from the next post on, a bundle of widgets assembled with layouts goes here.window.show()— creating a window does not display it. It appears on screen only after you call show.
app.exec() — what the event loop means #
The last line, app.exec(), is the fork in the road between a GUI program and a script. The Python scripts so far ran top to bottom and exited when done. A GUI app, though, cannot know when the user will click a button or close the window, so it needs a structure that waits without ending.
app.exec() starts the event loop. Whenever an event arrives — a mouse click, keyboard input, a window resize — it delivers the event to the right widget, then goes back to waiting for the next one, in an endless cycle. When you close the window, the loop ends and returns an exit code, and sys.exit() hands that code to the operating system.
One rule follows from this structure: block the event loop for long and the whole app freezes up. If the function connected to a button click runs a ten-second computation, no screen updates and no click handling happen in the meantime. We face this problem and its solution head-on in #6, where we cover threads.
Wrap-up #
This post comes down to three points.
- Among Python GUI options, PySide6 is the official Qt binding, and its LGPL license lets you build even commercial apps for free.
- The standard skeleton of a GUI app is one QApplication, a class inheriting QMainWindow, a show call, and
app.exec(). app.exec()starts the event loop that waits for and dispatches events, and blocking that loop freezes the app.
In the next post, “Build a Desktop App with PySide6 #2: Widgets and Layouts — Assembling the Screen,” we move past the one-button window and assemble an input field, a list, and buttons with layouts into the screen skeleton of a todo app. That app is the hands-on material we will keep extending throughout the series.