Ir al contenido

Pyzed vs tkinter — una alternativa moderna a tkinter

tkinter es el toolkit gráfico que viene con Python. Está en todas partes, es pequeño y arranca rápido, por eso casi todo el mundo empieza con él. También se le nota la edad: widgets con aspecto de los 2000, un modelo de diseño repartido entre pack, grid y place, y nada de modo oscuro, recarga en caliente ni diseñador visual. CustomTkinter moderniza el aspecto sobre tkinter, y también aparece en los números de abajo.

Pyzed tkinter
Instalación pip install pyzed-ui Ya está (en Linux, a veces un paquete python3-tk)
Aspecto Componentes modernos listos, temas, modo oscuro Tk clásico, o temas ttk
Modelo Estado + view(): la interfaz se reconstruye desde tus datos Widgets que creas, guardas y modificas
Diseño Filas, columnas, rejillas, pilas, docks, anclas pack, grid, place
Dibujo GPU (wgpu), CPU de respaldo Tk, en la CPU
Diseñador, recarga Pyzed Design, pyzed dev Diseñadores de terceros; sin recarga en caliente
Widgets avanzados Editor de código, paleta de comandos, árbol de archivos, pestañas, avisos, markdown… Text, Canvas, Treeview; el resto lo haces tú
Accesibilidad Teclado; sin lectores de pantalla todavía Limitada

Ambos archivos son las apps de referencia de los benchmarks, contadas línea a línea.

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)
tkinter
"""Reference app: a counter with -, + and Reset."""
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("Counter")
root.geometry("320x200")
count = tk.IntVar(value=0)
frame = ttk.Frame(root)
frame.place(relx=0.5, rely=0.5, anchor="center")
ttk.Label(frame, textvariable=count, font=("TkDefaultFont", 24)).pack(pady=8)
buttons = ttk.Frame(frame)
buttons.pack()
ttk.Button(buttons, text="-", command=lambda: count.set(count.get() - 1)).pack(side="left", padx=4)
ttk.Button(buttons, text="+", command=lambda: count.set(count.get() + 1)).pack(side="left", padx=4)
ttk.Button(buttons, text="Reset", command=lambda: count.set(0)).pack(side="left", padx=4)
root.mainloop()

Añadir con el botón o con Enter, marcar tareas, borrarlas y mostrar cuántas quedan. En tkinter, mantener la pantalla sincronizada con los datos es cosa tuya (refresh() destruye y vuelve a crear las filas); en Pyzed, view() siempre describe los datos actuales.

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)
tkinter
"""Reference app: add tasks (button or Enter), tick them off, delete them, see how many are left."""
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("To-do")
root.geometry("420x480")
tasks = [] # [title, BooleanVar]
left = ttk.Label(root, font=("TkDefaultFont", 14))
left.pack(anchor="w", padx=16, pady=(16, 8))
entry_row = ttk.Frame(root)
entry_row.pack(fill="x", padx=16)
draft = tk.StringVar()
entry = ttk.Entry(entry_row, textvariable=draft)
entry.pack(side="left", fill="x", expand=True)
canvas = tk.Canvas(root, highlightthickness=0)
bar = ttk.Scrollbar(root, command=canvas.yview)
canvas.configure(yscrollcommand=bar.set)
bar.pack(side="right", fill="y", pady=12)
canvas.pack(fill="both", expand=True, padx=16, pady=12)
inner = ttk.Frame(canvas)
canvas.create_window((0, 0), window=inner, anchor="nw")
inner.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
def refresh():
for child in inner.winfo_children():
child.destroy()
for task in tasks:
row = ttk.Frame(inner)
ttk.Checkbutton(row, text=task[0], variable=task[1], command=refresh).pack(side="left")
ttk.Button(row, text="Delete", command=lambda t=task: (tasks.remove(t), refresh())).pack(side="right")
row.pack(fill="x", pady=2)
left.configure(text=f"{sum(not done.get() for _, done in tasks)} left")
def add(_event=None):
if draft.get().strip():
tasks.append([draft.get().strip(), tk.BooleanVar(value=False)])
draft.set("")
refresh()
ttk.Button(entry_row, text="Add", command=add).pack(side="left", padx=(8, 0))
entry.bind("<Return>", add)
refresh()
root.mainloop()

Medianas de los benchmarks (Linux, sin GPU: renderizado por software, que penaliza al renderizador por GPU de Pyzed y favorece el dibujo por CPU de tkinter).

Medición (menor es mejor en todas las filas)Pyzed 1.2.0tkinter Tk 9.0.3
Importar la biblioteca35 ms5.3 ms
Primer fotograma, 10 filas165 ms96 ms
Primer fotograma, 1.000 filas243 ms157 ms
Memoria (RSS), 10 filas151 MB25 MB
Memoria (RSS), 1.000 filas161 MB34 MB
Actualizar 1.000 filas18 ms7.7 ms
Líneas: contador19 líneas15 líneas
Líneas: lista de tareas31 líneas39 líneas

tkinter gana en arranque y tamaño: son unos pocos megas de C que ya vienen con Python, y dibuja con la CPU, justo lo que una máquina sin GPU hace bien. Pyzed trae un renderizador por GPU, un motor de texto y decenas de componentes, y aun así abre una ventana con 1.000 filas en una fracción de segundo.

tkinter Pyzed
root = tk.Tk() … root.mainloop() class MiApp(pz.App) … pz.run(MiApp)
root.title(...), root.geometry("400x300") atributos de clase title = ..., size = (400, 300)
tk.StringVar, IntVar atributos normales: self.nombre = ""
widget.configure(text=...) cambia el atributo; view() lo muestra
ttk.Label, ttk.Button, ttk.Entry ui.label, ui.button, ui.input
command=callback on_press=callback (o un mensaje para update())
pack(side="left") / grid(row, column) pz.row([...]), pz.column([...]), pz.grid(...)
Canvas + Scrollbar + Frame interior pz.scrollable(...)
ttk.Treeview ui.tree, ui.table
root.after(ms, fn) pz.every(segundos, mensaje) en subscriptions(), o un callback async
messagebox ui.confirm, ui.prompt, ui.modal
filedialog Todavía no hay diálogo de archivos nativo: usa un ui.tree de archivos o una biblioteca auxiliar
ttk.Style temas y estilos
  1. Saca el estado de los widgets. Cada StringVar, IntVar y cada valor que lees con .get() pasa a ser un atributo de tu App. Ese es casi todo el trabajo.
  2. Sustituye el código de actualización por view(). Las funciones que destruyen y reconstruyen frames o hacen configure sobre etiquetas desaparecen: escribe view() una vez a partir de los atributos.
  3. Traduce los diseños uno a uno. Un Frame empaquetado en horizontal es un pz.row, en vertical un pz.column; grid pasa a pz.grid; el espaciado y el relleno son argumentos en vez de padx/pady en cada widget.
  4. Temporizadores e hilos. Los bucles con root.after pasan a subscriptions(); el trabajo largo va en un callback async def o en un hilo, y su resultado vuelve como mensaje.
  5. Deja tkinter donde encaja. Un diálogo de cinco líneas en un script que debe funcionar con un Python pelado sigue siendo un buen caso para tkinter.
  • Elige tkinter para utilidades diminutas y material didáctico que debe funcionar sin instalar nada, o cuando tus usuarios necesitan lectores de pantalla en plataformas donde Tk los soporta.
  • Elige Pyzed cuando la app deba verse actual, crecer más allá de unas pocas ventanas, diseñarse visualmente, recargarse mientras trabajas y distribuirse como un ejecutable pulido.