Skip to content

Pyzed vs Dear PyGui

Dear PyGui wraps Dear ImGui, the immediate-mode toolkit used for debug tools in game engines. It is GPU-drawn and very fast, and it shines for real-time plots, node editors and internal tools. Its look is the ImGui “tool” look, and it redraws continuously, which suits a dashboard that changes every frame more than a form that changes when you click.

Pyzed Dear PyGui
Model State + view() (Elm architecture) Items created once with tags, changed with set_value / configure_item
Redraws When something changes Every frame, by default
Look Modern ready-made components, themes, dark mode ImGui tool style, configurable with themes
Text Full shaping, any script, emoji, rich text and markdown Fonts and glyph ranges you load yourself
Plots Sparklines, bar and donut charts, a canvas Excellent built-in plotting (ImPlot)
Desktop features Menus, shortcuts, multi-window, docks, code editor Docking and viewports from ImGui
Designer, hot reload Pyzed Design, pyzed dev –
Accessibility Keyboard; no screen readers yet No screen readers
Pyzed
"""Reference app: a counter with -, + and Reset."""
import pyzed as pz
from 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)
Dear PyGui
"""Reference app: a counter with -, + and Reset."""
import dearpygui.dearpygui as dpg
dpg.create_context()
count = 0
def change(step):
global count
count = 0 if step is None else count + step
dpg.set_value("count", str(count))
with dpg.window(tag="main"):
dpg.add_text("0", tag="count")
with dpg.group(horizontal=True):
dpg.add_button(label="-", callback=lambda: change(-1))
dpg.add_button(label="+", callback=lambda: change(1))
dpg.add_button(label="Reset", callback=lambda: change(None))
dpg.create_viewport(title="Counter", width=320, height=200)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.set_primary_window("main", True)
dpg.start_dearpygui()
dpg.destroy_context()
Pyzed
"""Reference app: add tasks (button or Enter), tick them off, delete them, see how many are left."""
import pyzed as pz
from 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)
Dear PyGui
"""Reference app: add tasks (button or Enter), tick them off, delete them, see how many are left."""
import dearpygui.dearpygui as dpg
dpg.create_context()
tasks = [] # [title, done]
def refresh():
dpg.delete_item("list", children_only=True)
for task in tasks:
with dpg.group(horizontal=True, parent="list"):
dpg.add_checkbox(label=task[0], default_value=task[1],
callback=lambda s, value, t=task: (t.__setitem__(1, value), refresh()))
dpg.add_button(label="Delete", callback=lambda s, a, t=task: (tasks.remove(t), refresh()))
dpg.set_value("left", f"{sum(not done for _, done in tasks)} left")
def add():
text = dpg.get_value("entry").strip()
if text:
tasks.append([text, False])
dpg.set_value("entry", "")
refresh()
with dpg.window(tag="main"):
dpg.add_text("0 left", tag="left")
with dpg.group(horizontal=True):
dpg.add_input_text(tag="entry", width=300, on_enter=True, callback=add)
dpg.add_button(label="Add", callback=add)
dpg.add_child_window(tag="list")
dpg.create_viewport(title="To-do", width=420, height=480)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.set_primary_window("main", True)
dpg.start_dearpygui()
dpg.destroy_context()

Medians from the benchmarks (Linux, no GPU: both render through Mesa’s software drivers).

Measurement (lower is better in every row)Pyzed 1.2.0Dear PyGui 2.3.1
Import the library35 ms17 ms
First frame, 10 rows165 ms133 ms
First frame, 1,000 rows243 ms152 ms
Memory (RSS), 10 rows151 MB123 MB
Memory (RSS), 1,000 rows161 MB125 MB
Update 1,000 rows18 ms6.6 ms
Lines: counter19 lines19 lines
Lines: to-do list31 lines29 lines
Dear PyGui Pyzed
create_context, create_viewport, start_dearpygui pz.run(MyApp)
with dpg.window(...) the root returned by view()
dpg.add_text(tag="x") + dpg.set_value("x", ...) ui.label(self.x): change self.x
callback=fn on_press=fn
dpg.group(horizontal=True) pz.row
dpg.child_window pz.scrollable, ui.card, ui.panel
dpg.theme() + bind_item_theme themes and styles
dpg.add_plot ui.bar_chart, ui.sparkline, or pz.canvas for custom drawing
  • Choose Dear PyGui for real-time plots, node editors, instrument panels and debug tools where the ImGui look is fine.
  • Choose Pyzed for end-user apps: forms, lists, editors and dashboards that should look polished, with proper text, a designer, hot reload and packaging.