[Python-es] ejecutar una función cada cierto tiempo.

Yoel Benítez Fonseca mark en grm.uci.cu
Vie Abr 16 16:10:34 CEST 2010


H! Mira este es un post a un blog interno a la intranet de mi
institución donde machaco un poco el tema de crear demonios en Python,
no se si es exactamente lo que quieres pero creo que te puede servir:

-----------

Demonios en Python no ¡Demonios Python!

En la administración de la red es muy común encontrarnos en situaciones
donde es necesario escribir aplicaciones para que sean ejecutadas de
forma continua y en segundo plano, en otras palabras estamos hablando de
un Demonio (Daemon en ingles), en este artículo veremos como se puede
desarrollar un sistema de este tipo en Python. 

En los sistemas *nix estas aplicaciones abundan y por lo general
en /etc/init.d/* encontramos los scripts que se encargan de ejecutarlas.
Una característica que comparten todos estos sistemas es su capacidad de
desligarse de su proceso padre (un concepto de sistemas operativos) ya
que una vez que son ejecutados podemos cerrar la sesión o la terminal y
estas siguen corriendo. Estos conceptos y otros aparecen explicados en
el libro The Linux Programmer’s Guide del proyecto TLDP.

Con Python podemos escribir sistemas con esta capacidad ya que el
lenguaje viene con una librería dependiente del sistema, por supuesto,
que permite a un proceso desligarse de su padre que en esencia es lo que
necesitamos hacer. Estas funciones podemos encontrarlas en los módulos
os y subprocess de la librería estándar de Python.

O para hacerlo más sencillo, existe un paquete que podemos instalar que
engloba toda la funcionalidad que deseamos en un solo modulo para hacer
un Demonio, este paquete podemos descargándolo desde ,el sitio web de
los autores [http://www.livinglogic.de/Python/daemon/index.html] o
instalarlo desde el repositorio de Ubuntu o Debian:

$ sudo apt-get install python-ll-core
El como usarlo es muy sencillo, y podemos verlo en el siguiente ejemplo:

01
from ll import daemon
02
 
03
counter = daemon.Daemon(
04
        stdin="/dev/null",
05
        stdout="/tmp/daemon.log",
06
        stderr="/tmp/daemon.log",rea
un
07
        pidfile="/var/run/counter/counter.pid",
08
        user="nobody"
09
)
10
 
11
if __name__ == "__main__":
12
        if
counter.service():aplicación no
13
                import sys, os, time
14
                c = 0
15
                while True:
16
                        # escribe
aqui el contenido del programa
17
                        sys.stdout.write('%d: %s\n' % (c, time.ctime(time.time())))
18
                        sys.stdout.flush()
19
                        c += 1
20
                        time.sleep(1)
Veamos paso a paso: 

      * La primera linea from ll import daemon se importa el modulo que
        nos permite demonizar el script.
      * En la linea 03 se configuran la entrada estándar, salida
        estándar y salida de error estándar del programa, en este caso
        podemos direccionar estas a archivos de nuestra conveniencia, se
        pueden generar archivos de log que permitan la depuración del
        programa ya que como este no se ejecuta en primar plano la tarea
        de depurar el programa puede ser engorrosa. Otra cosa que
        debemos tener en cuenta es que el parámetro user define el
        usuario con el que se va a ejecutar la aplicación, normalmente
        los programar iniciados por los scripts en /etc/init.d/* son
        ejecutados con privilegios de root aunque no tiene que ser
        necesariamente así.
      * Después de la llamada, en la linea 12, a counter.service()
        nuestro programa ya es un demonio y como la idea es que nuestra
        aplicación este corriendo siempre la linea de código 15 crea un
        ciclo infinito que nunca dejara de correr.

Aunque este ejemplo es muy sencillo, ya que el programa en sí lo único
que hace es incrementar un contador c y escribir este más la fecha y
hora en la salida estándar (recuerden que esta direccionada a un
archivo: /tmp/daemon.log) para luego dormir durante 1 segundo
time.sleep(1), demuestra el esqueleto básico que podemos usar para crear
nuestro demonio.

La segunda parte de nuestro demonio es crear un bash script para ponerlo
en /etc/init.d donde residen los scripts de las aplicaciones que se
ejecutan cuando el sistema operativo arranca, el esqueleto es sencillo:

01
#! /bin/bash
02
 
03
PATH=/sbin:/bin:/usr/sbin:/usr/bin
04
[ -f /etc/default/rcS ]
&& . /etc/default/rcS
05
. /lib/lsb/init-functions
06
APPHOME=/usr/local/app
07
 
08
case "$1" in
09
    start)
10
        cd $APPHOME
11
        python miscript.py start
12
        ;;
13
    restart|reload|force-reload)
14
        # cambiar esto para realizar
estas operaciones
15
        # si fuera necesario para
nuestro demonio
16
        echo "Error: argument '$1'
not supported" >&2
17
        exit 3
18
        ;;
19
    stop)
20
        cd $APPHOME
21
        python miscript.py stop
22
        ;;
23
    *)
24
        echo "Usage: $0 start|stop"
>&2
25
        exit 3
26
        ;;
27
esac
Explicar este script esta fuera del propósito de este artículo, solo
algunas cosas a tener en cuenta:

      * Guardar este bash script y cambiar sus permisos a ejecución: 
        $ chmod +x script.sh
      * Cambiar el valor de APPHOME por el nombre del directorio donde
        recide el demonio
      * miscript.py se refiere al script en python y hay que cambiarlo
        al nombre que le pongamos nosotros.
      * Para instalar el script de arranque (por lo menos en Ubuntu o
        Debian) debemos guardar el bash script en /etc/init.d y luego
        ejecutar: 
        $ sudo update-rc.d script.sh defaults
        
        cambiando script.sh por el nombre que hayamos usado. Después de
        esto nuestro programa en Python se ejecutará y detendrá con los
        arranques del sistema o podemos arrancarlo manualmente con:
        
        $ sudo /etc/init.d/script.sh start
        y detenerlo con
        
        $ sudo /etc/init.d/script.sh stop

Depurar programas que se ejecuten de esta manera es muy difícil teniendo
en cuenta que este tipo de aplicaciones no están vinculadas a una
terminal y por lo tanto no pueden imprimir mensajes en la pantalla, de
ahí la importancia de direccionar la salida estándar y salida de error
estándar a archivos. También se puede usar el modulo logging de Python
para generar archivos de logs que puedan usarse en la depuración. Otra
cosa que podemos hacer es escribir (si es posible) nuestro demonio como
una aplicación normal hasta que estemos seguros que es correcta y
después demonizarla.

Con esto terminamos, aunque parece complicado una vez que se tiene el
esqueleto básico lo que resta es escribir la aplicación. Espero sus
comentarios y experiencias propias.
-----------


El jue, 15-04-2010 a las 12:57 -0400, Boris Perez Canedo escribió:
> Hola a todos.
> 
> Necesito hacer un script que corra constantemente y cada cierto tiempo
> ejecute una función:
> 
> Se que podría utilizar al s.o. (programar una tarea) pero me interesa
> hacerlo con python porque es parte de un sistema que estoy haciendo y
> ha de funcionar en cualquier s.o.
> 
> La idea que tengo es la siguiente:
> 
> En un archivo de configuración tengo el día de la semana y la hora en
> que ha de correrse la función, sería algo así:
> 
> 0-08:00:00 ó 3600
> 
> Con expresiones regulares separo el día de la semana (en este caso 0
> es Lunes) y la hora (08:00:00). Podría solo aparecer un número, en
> este caso significa que la función se correrá una vez que transcurra
> ese tiempo en segundos.
> 
> Para el segundo caso me resulta sencillo hacerlo y sería algo como
> esto:
> 
> def funcion():
>    "hacer algo"
> 
> def Para2doCaso(conf_file):
>       while True:
>                  dia, hora = obtener_config(conf_file)
>                  if hora == "":
>                            #estamos en el segundo caso (dia contiene
> los segundos a esperar).
>                            funcion() # ejecuto mi funcion
>                            time.sleep(int(dia)) # espero
>                  else:
>                         # aqui viene la otra forma de configuracion y
> mi duda.
>                  
> Pongo dentro del ciclo dia, hora = obtener_config(conf_file) porque
> aunque es cierto que en el segundo caso puede llegar a ejecutarse la
> función cada, por ejemplo, 1 segundo, no es lo común, lo normal es que
> se ejecute de 30 o 40 minutos en adelante y necesito que sea revisada
> la configuración ya que puede llegarse a cambiar en ese tiempo.
> 
> 
> Gracias por adelantado.
> 
> Saludos,
> Boris.
>        
>         
>    
> 
> 
> 
> 
> ______________________________________________________________________
> 
> La mejor vacuna contra el virus A(H1N1) es la higiene personal
> 
> _______________________________________________
> Python-es mailing list
> Python-es en python.org
> http://mail.python.org/mailman/listinfo/python-es
> FAQ: http://python-es-faq.wikidot.com/


-- 
Yoel Benítez Fonseca <mark en grm.uci.cu>
Universidad de Ciencias Informáticas




Más información sobre la lista de distribución Python-es