Pyzed vs PyQt and PySide (Qt for Python)
Qt is the most complete GUI framework you can use from Python, through PySide6 (by The Qt Company, LGPL) or PyQt6 (by Riverbank, GPL or commercial). It has everything: charts, multimedia, an embedded Chromium, 3D, printing, accessibility, and thirty years of production use. That breadth comes with weight: a large download, a C++ object model (signals, slots, parents, layouts) and licensing to think about.
In short
Section titled “In short”| Pyzed | PyQt6 / PySide6 | |
|---|---|---|
| License | MIT | PyQt: GPL v3 or commercial · PySide: LGPL v3 or commercial |
| Model | State + view(); plain Python callbacks |
Object tree, signals and slots, or declarative QML |
| Look | Modern components and themes, out of the box | Native-like styles; modern looks through QSS or QML |
| Designer | Pyzed Design, generates Python | Qt Designer / Creator (.ui files), Qt Design Studio (QML) |
| Hot reload | pyzed dev, keeps state |
QML preview in Qt tools; Widgets code needs a restart |
| Breadth | Desktop app essentials + IDE components | Almost anything, through dozens of modules |
| Mobile | – | PySide6 on Android (technical preview) |
| Accessibility | Keyboard; no screen readers yet | Full screen reader support |
The same counter
Section titled “The same counter”"""Reference app: a counter with -, + and Reset."""
import pyzed as pzfrom pyzed import ui
class Counter(pz.App): title = "Counter" size = (320, 200)
def __init__(self): self.count = 0
def add(self, step): self.count += step
def view(self): return pz.center(pz.column([ ui.heading(str(self.count)), pz.row([ ui.button(icon="minus", on_press=lambda: self.add(-1)), ui.button(icon="plus", on_press=lambda: self.add(1), variant="solid"), ui.button("Reset", lambda: setattr(self, "count", 0), variant="ghost"), ], spacing=8), ], spacing=16, align="center"))
pz.run(Counter)"""Reference app: a counter with -, + and Reset."""
import sys
from PySide6.QtCore import Qtfrom PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
class Counter(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Counter") self.resize(320, 200) self.count = 0 self.label = QLabel("0", alignment=Qt.AlignmentFlag.AlignCenter) self.label.setStyleSheet("font-size: 24px") buttons = QHBoxLayout() for text, step in (("-", -1), ("+", 1), ("Reset", None)): button = QPushButton(text) button.clicked.connect(lambda _=False, s=step: self.change(s)) buttons.addWidget(button) layout = QVBoxLayout(self) layout.addStretch() layout.addWidget(self.label) layout.addLayout(buttons) layout.addStretch()
def change(self, step): self.count = 0 if step is None else self.count + step self.label.setText(str(self.count))
app = QApplication(sys.argv)window = Counter()window.show()app.exec()The same to-do list
Section titled “The same to-do list”In Qt Widgets you build the widgets once and keep them in sync with your data by hand (refresh() deletes
and recreates the rows). In Pyzed you only describe the rows for the current data.
"""Reference app: add tasks (button or Enter), tick them off, delete them, see how many are left."""
import pyzed as pzfrom pyzed import ui
class Todo(pz.App): title = "To-do" size = (420, 480)
def __init__(self): self.tasks = [] # [title, done] self.draft = ""
def add(self): if self.draft.strip(): self.tasks.append([self.draft.strip(), False]) self.draft = ""
def toggle(self, task, done): task[1] = done
def row(self, task): return pz.row([ ui.checkbox(task[1], lambda done: self.toggle(task, done)), ui.label(task[0], color="text_muted" if task[1] else "text", width="fill"), ui.icon_button("trash-2", lambda: self.tasks.remove(task)), ], spacing=8, align="center")
def view(self): left = sum(not done for _, done in self.tasks) return pz.column([ ui.heading(f"{left} left", level=3), pz.row([ ui.input(self.draft, lambda text: setattr(self, "draft", text), on_submit=self.add), ui.button("Add", self.add, variant="solid"), ], spacing=8), pz.scrollable(pz.column([self.row(t) for t in self.tasks], spacing=4), height="fill"), ], spacing=12, padding=16)
pz.run(Todo)"""Reference app: add tasks (button or Enter), tick them off, delete them, see how many are left."""
import sys
from PySide6.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QScrollArea, QVBoxLayout, QWidget)
class Todo(QWidget): def __init__(self): super().__init__() self.setWindowTitle("To-do") self.resize(420, 480) self.tasks = [] # [title, done] self.left = QLabel() self.left.setStyleSheet("font-size: 16px; font-weight: bold") self.entry = QLineEdit() self.entry.returnPressed.connect(self.add) add = QPushButton("Add") add.clicked.connect(self.add) entry_row = QHBoxLayout() entry_row.addWidget(self.entry) entry_row.addWidget(add) self.list = QVBoxLayout() inner = QWidget() column = QVBoxLayout(inner) column.addLayout(self.list) column.addStretch() scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setWidget(inner) layout = QVBoxLayout(self) layout.addWidget(self.left) layout.addLayout(entry_row) layout.addWidget(scroll) self.refresh()
def add(self): if self.entry.text().strip(): self.tasks.append([self.entry.text().strip(), False]) self.entry.clear() self.refresh()
def toggle(self, task, done): task[1] = done self.refresh()
def remove(self, task): self.tasks.remove(task) self.refresh()
def refresh(self): while self.list.count(): item = self.list.takeAt(0) item.widget().deleteLater() for task in self.tasks: row = QWidget() line = QHBoxLayout(row) check = QCheckBox(task[0]) check.setChecked(task[1]) check.toggled.connect(lambda done, t=task: self.toggle(t, done)) delete = QPushButton("Delete") delete.clicked.connect(lambda _=False, t=task: self.remove(t)) line.addWidget(check, 1) line.addWidget(delete) self.list.addWidget(row) self.left.setText(f"{sum(not done for _, done in self.tasks)} left")
app = QApplication(sys.argv)window = Todo()window.show()app.exec()Measured
Section titled “Measured”Medians from the benchmarks (Linux, no GPU: software rendering). The install size
for Qt is pyside6-essentials only; the full pyside6 package with add-ons is several times larger.
| Measurement (lower is better in every row) | Pyzed 1.2.0 | PySide6 (Qt) 6.12.0 |
|---|---|---|
| Import the library | 35 ms | 68 ms |
| First frame, 10 rows | 165 ms | 135 ms |
| First frame, 1,000 rows | 243 ms | 217 ms |
| Memory (RSS), 10 rows | 151 MB | 72 MB |
| Memory (RSS), 1,000 rows | 161 MB | 83 MB |
| Update 1,000 rows | 18 ms | 24 ms |
| Lines: counter | 19 lines | 28 lines |
| Lines: to-do list | 31 lines | 62 lines |
From Qt concepts to Pyzed
Section titled “From Qt concepts to Pyzed”| Qt | Pyzed |
|---|---|
QApplication + QMainWindow + app.exec() |
class MyApp(pz.App) + pz.run(MyApp) |
QVBoxLayout, QHBoxLayout, QGridLayout |
pz.column, pz.row, pz.grid |
QLabel, QPushButton, QLineEdit, QCheckBox |
ui.label, ui.button, ui.input, ui.checkbox |
clicked.connect(slot) |
on_press=method |
| custom signals | messages sent to update(), or pz.post(message) from a thread |
QTimer |
pz.every(...) in subscriptions() |
QThread / QRunnable |
async def callbacks, generators, or plain threads |
QDockWidget, QTabWidget, QSplitter |
ui.workspace, ui.tab_bar, ui.split |
QTreeView / QTableView + models |
ui.tree, ui.table, ui.virtual_list from Python lists |
| QSS stylesheets | themes and pz.Style |
QShortcut, QAction |
on_key(combo), commands and keys="ctrl-s" on buttons |
Qt Designer .ui files |
Pyzed Design .pzform files with Python handlers |
Migration tips
Section titled “Migration tips”- Start from the data, not the widgets. List what your
QWidgetsubclasses store (self.items,self.current…) and make those the attributes of yourApp. Everything that copies them into widgets (setText,setChecked, model resets) disappears. - Turn slots into methods. A slot connected with
clicked.connect(self.save)becomeson_press=self.save. - Replace models with lists. A
QAbstractListModelover a Python list becomes the list itself plusui.virtual_listwhen it is long. - Keep Qt where you depend on it. If the app needs
QtWebEngine,QtMultimedia, printing or 3D, Qt is still the right tool for that app.
When to choose which
Section titled “When to choose which”- Choose Qt when you need its breadth, a screen-reader-accessible app today, or decades-proven stability across a large team, and its size and license fit your project.
- Choose Pyzed when you want a modern-looking desktop app with far less code and ceremony, a permissive MIT license, a much smaller install, hot reload and AI assistance built into the workflow.