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.
En resumen
Sección titulada «En resumen»| 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 |
El mismo contador
Sección titulada «El mismo contador»Ambos archivos son las apps de referencia de los benchmarks, contadas línea a línea.
"""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 tkinter as tkfrom 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()La misma lista de tareas
Sección titulada «La misma lista de tareas»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.
"""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 tkinter as tkfrom 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.0 | tkinter Tk 9.0.3 |
|---|---|---|
| Importar la biblioteca | 35 ms | 5.3 ms |
| Primer fotograma, 10 filas | 165 ms | 96 ms |
| Primer fotograma, 1.000 filas | 243 ms | 157 ms |
| Memoria (RSS), 10 filas | 151 MB | 25 MB |
| Memoria (RSS), 1.000 filas | 161 MB | 34 MB |
| Actualizar 1.000 filas | 18 ms | 7.7 ms |
| Líneas: contador | 19 líneas | 15 líneas |
| Líneas: lista de tareas | 31 líneas | 39 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.
De los conceptos de tkinter a Pyzed
Sección titulada «De los conceptos de tkinter a Pyzed»| 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 |
Consejos de migración
Sección titulada «Consejos de migración»- Saca el estado de los widgets. Cada
StringVar,IntVary cada valor que lees con.get()pasa a ser un atributo de tuApp. Ese es casi todo el trabajo. - Sustituye el código de actualización por
view(). Las funciones que destruyen y reconstruyen frames o hacenconfiguresobre etiquetas desaparecen: escribeview()una vez a partir de los atributos. - Traduce los diseños uno a uno. Un
Frameempaquetado en horizontal es unpz.row, en vertical unpz.column;gridpasa apz.grid; el espaciado y el relleno son argumentos en vez depadx/padyen cada widget. - Temporizadores e hilos. Los bucles con
root.afterpasan asubscriptions(); el trabajo largo va en un callbackasync defo en un hilo, y su resultado vuelve como mensaje. - 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.
Cuándo elegir cada uno
Sección titulada «Cuándo elegir cada uno»- 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.