Twisted + PyGTK

Hace unas semanas empece a interesarme en Twisted y en programacion asincronica en general. La verdad es que encuentro muy amigable la forma en la que Twisted maneja los callbacks y los eventos.

He estado trabajando en un programa con una interfaz grafica hecha con Pygtk que usa un thread para cada tarea y me pregunte si es posible tener ese mismo programa corriendo en un solo thread asincronicamente. Es verdaderamente simple implementar codigo twisted en aplicaciones escritas en PyGtk, los dos son vasados en eventos y Twisted tiene una API nativa para ese objetivo.

Empecemos con un ejemplo simple de PyGTK:

import gtk
 
class Gui:
    def __init__(self):
        self.window = gtk.Window()
        self.window.set_default_size(200,200)
        self.window.set_title("Simple PyGTK program")
 
        self.vbox = gtk.VBox()
 
        self.button = gtk.Button("I'm a button!")
        self.label = gtk.Label("Nothing here")
 
        self.vbox.pack_start(self.button)
        self.vbox.pack_start(self.label)
 
        self.window.add(self.vbox)
 
        self.button.connect("clicked", self.on_clicked)
        self.window.connect("destroy", lambda x: gtk.main_quit())
        self.window.show_all()
        self.window.show()
 
    def on_clicked(self, widget):
       self.label.set_text("You clicked the button!") 
 
if __name__ == "__main__":
    app = Gui()
    #Empezamos el bucle de GTK
    gtk.main()

Este ejemplo usa el bucle de Gtk. Una aplicacion hecha en Twisted usa el Twisted reactor, por lo tanto, necesitamos que el reactor “entienda” las señales emitidas por GTK

from twisted.internet import gtk2reactor # for gtk-2.0
gtk2reactor.install()
 
#Tu código
#...
 
from twisted.internet import reactor
#this starts the reactor
reactor.run()

Ahora podemos reimplementar nuestro primer ejemplo y sería algo paredido a esto:
Now we can re-implement or first example and it would look like this

import gtk
from twisted.internet import gtk2reactor # for gtk-2.0
gtk2reactor.install() #Instalamos de gtk2 reactor
#NOTE: This have to be at top always, before starting the reactor
#NOTA: Esto tiene que estar siempre al principio, antes de importar el reactor
 
class Gui:
    def __init__(self):
        self.window = gtk.Window()
        self.window.set_default_size(200,200)
        self.window.set_title("Simple PyGTK program")
 
        self.vbox = gtk.VBox()
 
        self.button = gtk.Button("I'm a button!")
        self.label = gtk.Label("Nothing here")
 
        self.vbox.pack_start(self.button)
        self.vbox.pack_start(self.label)
 
        self.window.add(self.vbox)
 
        self.button.connect("clicked", self.on_clicked)
        self.window.connect("destroy", lambda x: gtk.main_quit())
        self.window.show_all()
        self.window.show()
 
    def on_clicked(self, widget):
       self.label.set_text("You clicked the button!") 
 
if __name__ == "__main__":
    app = Gui()
    #Basta de GTK!
    #gtk.main()
    from twisted.internet import reactor
    #Empezamos el loop
    reactor.run()

Ahora podemos felizmente disfrutar el poder de Twisted en nuestras aplicaciones graficas 🙂 . Para terminar, este es un simple ejemplo de callbacks al estilo Twisted

import gtk
from twisted.internet import gtk2reactor # for gtk-2.0
gtk2reactor.install() #Instalamos de gtk2 reactor
#NOTE: This have to be at top always, before starting the reactor
#NOTA: Esto tiene que estar siempre al principio, antes de importar el reactor
 
class Gui:
    def __init__(self):
        self.window = gtk.Window()
        self.window.set_default_size(200,200)
        self.window.set_title("Simple PyGTK program")
 
        self.vbox = gtk.VBox()
 
        self.button = gtk.Button("I'm a button!")
 
        self.time = time.time()
 
        self.label = gtk.Label("0")
 
        self.vbox.pack_start(self.button)
        self.vbox.pack_start(self.label)
 
        self.window.add(self.vbox)
 
        self.button.connect("clicked", self.on_clicked)
        self.window.connect("destroy", lambda x: gtk.main_quit())
        self.window.show_all()
        self.window.show()
        self.time = time.time()
 
    def on_clicked(self, widget=None):
        self.label_text = time.time() - self.time
        self.label.set_text("%.2fs" % self.label_text)
        #Twisted va a llamar a la funcion sefl.on_clicked cada 100ms
        reactor.callLater(.1, self.on_clicked)
 
if __name__ == "__main__":
    app = Gui()
    #Basta de GTK!
    #gtk.main()
    from twisted.internet import reactor
    #Empezamos el loop
    reactor.run()

Twisted tiene otras APIS para otros toolkits como Tkinter, wxPython, Win32(Windows) y PyUI.
Para mas info consultar la documentación oficial.