Pyzed vs Flet for Python desktop apps
Flet lets you build Flutter apps in Python. Your Python code runs in one process and sends the control tree to a Flutter client that draws it, on the desktop, on the web or on a phone. That reach is Flet’s big strength. Pyzed draws in the same process as your Python code, through a Rust renderer, and focuses on the desktop.
In short
Section titled “In short”| Pyzed | Flet | |
|---|---|---|
| Architecture | One process: Python + Rust renderer (Iced) | Python process + Flutter client process, talking over a protocol |
| Platforms | Windows, macOS, Linux | Windows, macOS, Linux, web, Android, iOS |
| Look | Modern code-editor style, dense, desktop-first | Material 3 or Cupertino, mobile-first |
| Model | State + view() rebuilt from data |
Mutable controls + page.update(); Flet 1.0 adds declarative components with hooks |
| Packaging | pyzed build (PyInstaller), no extra SDK |
flet build (needs the Flutter SDK) |
| Designer | Pyzed Design | – |
| Hot reload | Keeps app state | flet run restarts the app on save |
| Accessibility | Keyboard; no screen readers yet | Screen readers through Flutter’s semantics |
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 flet as ft
def main(page: ft.Page): page.title = "Counter" page.window.width, page.window.height = 320, 200 count = ft.Text("0", size=24)
def change(step): count.value = "0" if step is None else str(int(count.value) + step) page.update()
page.add(ft.Column([ count, ft.Row([ ft.IconButton(ft.Icons.REMOVE, on_click=lambda e: change(-1)), ft.IconButton(ft.Icons.ADD, on_click=lambda e: change(1)), ft.TextButton("Reset", on_click=lambda e: change(None)), ], alignment=ft.MainAxisAlignment.CENTER), ], horizontal_alignment=ft.CrossAxisAlignment.CENTER))
ft.run(main)The same to-do list
Section titled “The same to-do list”"""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 flet as ft
def main(page: ft.Page): page.title = "To-do" page.window.width, page.window.height = 420, 480 tasks = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True) left = ft.Text("0 left", size=18, weight=ft.FontWeight.BOLD)
def refresh(_=None): left.value = f"{sum(not row.controls[0].value for row in tasks.controls)} left" page.update()
def remove(row): tasks.controls.remove(row) refresh()
def add(_): if entry.value.strip(): row = ft.Row([ft.Checkbox(label=entry.value.strip(), on_change=refresh, expand=True)]) row.controls.append(ft.IconButton(ft.Icons.DELETE, on_click=lambda e: remove(row))) tasks.controls.append(row) entry.value = "" refresh()
entry = ft.TextField(expand=True, on_submit=add) page.add(left, ft.Row([entry, ft.Button("Add", on_click=add)]), tasks)
ft.run(main)Measured
Section titled “Measured”Medians from the benchmarks (Linux, no GPU: software rendering for both). Flet’s
memory includes its Flutter client process, which is where the window lives. Its install size counts the
Python packages plus the desktop client that flet downloads on first run.
| Measurement (lower is better in every row) | Pyzed 1.2.0 | Flet 1.0.4 |
|---|---|---|
| Import the library | 35 ms | 40 ms |
| First frame, 10 rows | 165 ms | 579 ms |
| First frame, 1,000 rows | 243 ms | 994 ms |
| Memory (RSS), 10 rows | 151 MB | 268 MB |
| Memory (RSS), 1,000 rows | 161 MB | 395 MB |
| Update 1,000 rows | 18 ms | 166 ms |
| Lines: counter | 19 lines | 17 lines |
| Lines: to-do list | 31 lines | 22 lines |
From Flet concepts to Pyzed
Section titled “From Flet concepts to Pyzed”| Flet | Pyzed |
|---|---|
ft.run(main) with main(page) |
pz.run(MyApp) with a pz.App class |
page.add(...), page.update() |
return the tree from view(); no manual update |
ft.Column, ft.Row, ft.Container |
pz.column, pz.row, pz.container |
ft.Text, ft.Button, ft.TextField, ft.Checkbox |
ui.label, ui.button, ui.input, ui.checkbox |
on_click=handler(e) |
on_press=method (no event argument needed) |
page.theme_mode = ft.ThemeMode.DARK |
theme = "One Dark" or pz.themes.auto(...) |
page.run_task(coro) |
return a coroutine from a callback |
ft.ListView |
ui.virtual_list |
ft.NavigationRail, ft.Tabs |
ui.workspace panels, ui.tab_bar |
Migration tips
Section titled “Migration tips”- Move control state into the app. Values you read back from controls (
field.value,checkbox.value) become attributes, set by the controls’ callbacks. - Delete
page.update()calls. Pyzed redraws after every callback. - Expect a denser UI. Material spacing is made for touch; Pyzed’s components are sized like a desktop
editor. Use
size="lg"variants or styles if you want more air.
When to choose which
Section titled “When to choose which”- Choose Flet when one codebase must run on the web and on phones, or you want Flutter’s Material widgets.
- Choose Pyzed for desktop-first apps: one process, no Flutter SDK to package, a visual designer, hot reload that keeps your state, and a dense, professional look.