Pyzed vs Kivy para apps de escritorio en Python
Kivy y Pyzed comparten la idea básica: dibujar cada widget por su cuenta en la GPU en vez de pedir widgets nativos al sistema operativo. Kivy nació para pantallas táctiles y funciona en Android e iOS, algo que Pyzed no hace. Pyzed está hecho para apps de productividad de escritorio y trae los componentes, temas y herramientas para ellas.
En resumen
Sección titulada «En resumen»| Pyzed | Kivy | |
|---|---|---|
| Plataformas | Windows, macOS, Linux | Windows, macOS, Linux, Android, iOS |
| Dibujo | wgpu (Vulkan, Metal, DX12, GL), CPU de respaldo | OpenGL ES 2 |
| Aspecto | Componentes de escritorio modernos, temas, modo oscuro | Estilo mínimo propio; Material con KivyMD |
| Descripción de la interfaz | Solo Python | Python más el lenguaje KV |
| Estado | Atributos normales, view() se reconstruye al cambiar |
Propiedades y bindings de Kivy |
| Detalles de escritorio | Menús, atajos, pestañas, árboles, docks, editor de código, varias ventanas | Pensado para táctil; las convenciones de escritorio son cosa tuya |
| Diseñador, recarga | Pyzed Design, pyzed dev |
Herramientas de terceros |
| Accesibilidad | Teclado; sin lectores de pantalla todavía | Sin lectores de pantalla |
El mismo contador
Sección titulada «El mismo contador»"""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."""
from kivy.app import Appfrom kivy.lang import Builderfrom kivy.properties import NumericProperty
KV = """AnchorLayout: BoxLayout: orientation: "vertical" size_hint: None, None size: 240, 120 spacing: 8 Label: text: str(app.count) font_size: 32 BoxLayout: spacing: 8 Button: text: "-" on_press: app.count -= 1 Button: text: "+" on_press: app.count += 1 Button: text: "Reset" on_press: app.count = 0"""
class CounterApp(App): count = NumericProperty(0) title = "Counter"
def build(self): return Builder.load_string(KV)
CounterApp().run()La misma lista de tareas
Sección titulada «La misma lista de tareas»La versión de Kivy usa un RecycleView, la forma recomendada por Kivy para mostrar listas, con el diseño de
la fila en KV.
"""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."""
from kivy.app import Appfrom kivy.lang import Builderfrom kivy.properties import BooleanProperty, ListProperty, NumericProperty, StringPropertyfrom kivy.uix.boxlayout import BoxLayout
KV = """<TaskRow>: size_hint_y: None height: 40 CheckBox: size_hint_x: None width: 40 active: root.done on_active: app.toggle(root.index, self.active) Label: text: root.title color: (.5, .5, .5, 1) if root.done else (1, 1, 1, 1) text_size: self.size valign: "middle" Button: text: "Delete" size_hint_x: None width: 80 on_press: app.remove(root.index)
BoxLayout: orientation: "vertical" padding: 16 spacing: 12 Label: text: f"{app.left} left" size_hint_y: None height: 32 font_size: 20 BoxLayout: size_hint_y: None height: 40 spacing: 8 TextInput: id: entry multiline: False on_text_validate: app.add(self) Button: text: "Add" size_hint_x: None width: 80 on_press: app.add(entry) RecycleView: data: app.rows viewclass: "TaskRow" RecycleBoxLayout: orientation: "vertical" size_hint_y: None height: self.minimum_height default_size_hint: 1, None default_size: None, 40"""
class TaskRow(BoxLayout): index = NumericProperty(0) title = StringProperty("") done = BooleanProperty(False)
class TodoApp(App): title = "To-do" rows = ListProperty([]) left = NumericProperty(0)
def build(self): return Builder.load_string(KV)
def refresh(self): self.rows = [{**row, "index": i} for i, row in enumerate(self.rows)] self.left = sum(not row["done"] for row in self.rows)
def add(self, entry): if entry.text.strip(): self.rows.append({"title": entry.text.strip(), "done": False}) entry.text = "" self.refresh()
def toggle(self, index, done): self.rows[index]["done"] = done self.refresh()
def remove(self, index): del self.rows[index] self.refresh()
TodoApp().run()Medianas de los benchmarks (Linux, sin GPU: los dos toolkits renderizan con el OpenGL/Vulkan por software de Mesa, así que entre ellos la comparación es justa).
| Medición (menor es mejor en todas las filas) | Pyzed 1.2.0 | Kivy 2.3.1 |
|---|---|---|
| Importar la biblioteca | 35 ms | 99 ms |
| Primer fotograma, 10 filas | 165 ms | 258 ms |
| Primer fotograma, 1.000 filas | 243 ms | 1,101 ms |
| Memoria (RSS), 10 filas | 151 MB | 146 MB |
| Memoria (RSS), 1.000 filas | 161 MB | 282 MB |
| Actualizar 1.000 filas | 18 ms | 59 ms |
| Líneas: contador | 19 líneas | 31 líneas |
| Líneas: lista de tareas | 31 líneas | 81 líneas |
De los conceptos de Kivy a Pyzed
Sección titulada «De los conceptos de Kivy a Pyzed»| Kivy | Pyzed |
|---|---|
App.build() devuelve el widget raíz |
App.view() devuelve el árbol, cada vez que cambia el estado |
NumericProperty, StringProperty, ListProperty |
atributos normales |
Reglas KV (<MiFila>:) |
funciones de Python que devuelven widgets, o componentes |
BoxLayout(orientation=...) |
pz.row / pz.column |
GridLayout, AnchorLayout, FloatLayout |
pz.grid, pz.center / alineación, pz.stack + pz.anchor_layout |
ScrollView, RecycleView |
pz.scrollable, ui.virtual_list |
on_press: app.hacer() |
on_press=self.hacer |
Clock.schedule_interval |
pz.every(...) en subscriptions() |
Animation(...) |
pz.Animation y transiciones |
Window.clearcolor, temas de KivyMD |
temas |
Consejos de migración
Sección titulada «Consejos de migración»- Pasa el KV a
view(). Cada regla KV se convierte en una función que recibe datos y devuelve widgets. Los bindings comotext: str(app.count)pasan a ser expresiones normales:ui.heading(str(self.count)). - Olvídate de las propiedades. Las propiedades de Kivy existen para que los bindings observen cambios;
Pyzed vuelve a ejecutar
view()después de cada evento, así que bastan atributos normales. - Repiensa los patrones solo táctiles. En escritorio se esperan menús, atajos de teclado y estados al pasar el ratón, y los componentes de Pyzed los traen.
Cuándo elegir cada uno
Sección titulada «Cuándo elegir cada uno»- Elige Kivy cuando la misma app deba funcionar en móviles y tablets, o para quioscos táctiles y apps multimedia.
- Elige Pyzed para apps de escritorio que deban verse y comportarse como software de escritorio moderno, con diseñador, recarga en caliente y herramientas de IA, y sin un segundo lenguaje que aprender.