GLib IO Channels con Python

Python

Cómo utilizar los IO-Channels de GLib desde Python para implementar el patrón reactor.

Introducción

Los IO Channels de GLib son una forma muy elegante y eficiente de manejar múltiples fuentes de datos asíncronas simultáneas. En un sistema Unix cualquier entrada de datos se puede ver como un fichero. Eso permite que ficheros, pipes, terminales, puertos serie, sockets y casi cualquier cosa se puedan tratar de forma homogénea. Además, también se pueden utilizar eventos activados por tiempo.

En términos de diseño, se considera que el bucle de eventos de GLib es una implementación del patrón de diseño Reactor.

GLib para Python?

Es obvio que no tiene mucho sentido un módulo glib en Python, pues la mayoría de las features de esta librería ya las soporta de serie el lenguaje. Tampoco tiene sentido un módulo GObject pues Python soporta orientación a objetos de forma nativa.

Sin embargo, el bucle de eventos de GTK está realmente en GObject y esa es la razón principal de que haya un módulo GObject para Python. Todo esto viene muy bien porque no hay una implementación “estándar” de un bucle de eventos genérico para Python.

Resumiendo, el módulo gobject disponible en Python es la respuesta si lo que buscamos es un “reactor” genérico y portable.

Ingredientes

  • python
  • python-gtk2

Y sin más, un ejemplo

Lo que aparece a continuación es un programa completo (aunque inútil) que demuestra cómo se puede usar gobject para implementar un reactor con tres fuentes:

  • La entrada estándar (stdin)
  • Un socket UDP (vinculado al puerto 9000)
  • Un evento periódico (cada 3 segundos).

Los handlers deben devolver True, de otro modo son automáticamente eliminados después de su ejecución:

import sys, os, socket
import gobject
 
def file_handler(fd, condition):
    print os.read(fd.fileno(), 32)
    return True
 
def timer_handler():
    print 'timeout'
    return True
 
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.bind(('',9000))
 
gobject.timeout_add(3000, timer_handler)
gobject.io_add_watch(sys.stdin, gobject.IO_IN, file_handler)
gobject.io_add_watch(udp_sock.makefile(), gobject.IO_IN, file_handler)
gobject.MainLoop().run()

Para probar la fuente UDP se puede usar netcat:

$ nc -u host 9000

gobject.MainContext.iteration

Si por cualquier circunstancia necesitas crear tú el bucle de eventos y no puedes usar MainLoop, todavía puedes usar la gestión de eventos de gobject por medio de iteration().

Ten en cuenta que, en este caso, el tratamiento no es bloqueante. El bucle consumirá toda la CPU disponible, pero se supone que la necesitas para hacer algún tipo de cómputo intensivo. Si no es ese tu caso, deberías volver a plantearte si lo que te conviene es usar directamente el “mainloop”, como en el ejemplo del punto anterior.

Para el bucle anterior sería algo como:

[...]
gobject.io_add_watch(udp_sock.makefile(), gobject.IO_IN, file_handler)
while 1:
    gobject.main_context_default().iteration(False)
    ...tus tareas...

Pero atención, si necesitas asegurarte de que se sirve algún evento antes de seguir cada iteración, tendrás que ejecutar el método iteration() hasta que devuelva True:

[...]
gobject.io_add_watch(udp_sock.makefile(), gobject.IO_IN, file_handler)
while 1:
    while not gobject.main_context_default().iteration(): pass
    ...tareas...

Referencias