viernes, 17 de marzo de 2017

Programar eventos para ejecutar tareas



El módulo sched


El módulo sched implementa un programador o planificador de eventos para ejecutar tareas después de que transcurra un tiempo específico o, bien, para ejecutar en un momento determinado (por ejemplo, en una fecha y hora concretas).

El módulo sched se basa en la clase scheduler  que tiene métodos para declarar un programador; definir, ejecutar y cancelar eventos; y para consultar información sobre eventos pendientes de ejecución.


Declarar un programador


Para declarar un programador se utiliza el método constructor scheduler() que tiene como primer argumento una función para obtener la hora actual -que por defecto es monotonic()- y una función para introducir los tiempos de espera -que de forma predeterminada es sleep()-. Las dos funciones son del módulo time:

sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep)

Se pueden utilizar otras funciones para los argumentos de scheduler() pero las dos deben trabajar con la misma escala temporal.

# Declarar un programador con los valores por defecto:

programador1 = sched.scheduler()


Programar eventos y poner en marcha el programador


Para programar los eventos se emplean dos métodos: enterabs() y enter(). Mientras que el primero define los eventos de un programador para que se ejecuten en un momento específico, el segundo establece la ejecución para después de un tiempo de espera.

# Definir un evento para ejecutar en un momento específico

scheduler.enterabs(time, priority, action, argument=(), kwargs={})

# Definir un evento para ejecutar después de un tiempo de espera

scheduler.enter(delay, priority, action, argument=(), kwargs={})

Los dos métodos tienen los siguientes argumentos:

  • time/delay: en entertabs() define el momento de ejecución del evento y en enter() el tiempo de espera. El valor expresado debe ser compatible con el valor devuelto por la función indicada como primer argumento del constructor scheduler().
  • priority: establece la prioridad de ejecución cuando dos o más eventos tienen el mismo momento de ejecución. Cuanto más bajo es el número mayor prioridad tiene.
  • action: es la función a ejecutar.
  • argument: normalmente, es una tupla con los valores de los argumentos de la función de action. Opcional.
  • kwargs: es un diccionario que se puede pasar como argumento de la función de action. Opcional.

Los métodos enterabs() y enter() devuelven un valor que se puede utilizar para cancelar con posterioridad un evento con el método cancel(), siempre que el evento no haya iniciado su ejecución o, dicho de otro modo, cuando el evento esté en cola esperando a ejecutarse. El método empty() se utiliza para conocer si quedan eventos pendientes. La clase scheduler también cuenta con el atributo queue, muy útil, para consultar información sobre los eventos pendientes de ejecución.

Después de definir los eventos hay que poner en marcha el programador con el método run(). Este método hace que el programador espere (valiéndose de la función de retardo especificada en el método constructor) a que los eventos se vayan ejecutando, uno a uno, en el tiempo establecido y hasta que no quede ninguno pendiente. El método run() espera la ejecución porque tiene el argumento blocking con el valor True, por defecto. Si el valor de este argumento es False se ejecutarán sólo los eventos que vencen inmediatamente (por ejemplo, eventos con tiempo de espera en 0), devolviendo run(), si existe, el momento límite de la siguiente ejecución programada. Este argumento está disponible a partir de Python 3.3.


Programar eventos para ejecutar en un momento determinado


En el siguiente ejemplo se declara un programador con dos eventos para ejecutar una tarea 1 segundo después de poner en marcha el programador y la misma tarea cinco segundos después. El programador se crea con la función time.time() que devuelve el tiempo expresado en segundos. (Cualquier fecha-hora se puede expresar en segundos y viceversa. Ver: El módulo time)

import sched
import time

# Función que es llamada por los eventos
def abrir_cerrar(estado):
    print('TIEMPO:', int(time.time()))
    if estado:
        print("Abriendo compuertas...")
    else:
        print("Cerrando compuertas...")

# Declarar el programador
programador = sched.scheduler(time.time, time.sleep)

# Asignar el tiempo de comienzo (en segundos)
comienzo = int(time.time())
t1 = comienzo + 1   # Tiempo para abrir compuertas (1")
t2 = t1 + 4         # Tiempo para cerrar compuertas (5")
print('PROGRAMADOR INICIADO:', comienzo)

# Definir los dos eventos indicando: momento de ejecución, 
# prioridad, función a la que se llama y el valor que se
# pasa al argumento de la función:
programador.enterabs(t1, 1, abrir_cerrar, (1,))
programador.enterabs(t2, 1, abrir_cerrar, (0,))

# Poner en marcha el programador.
# El programa permanece a la espera hasta que se
# ejecuten los dos eventos. La ejecución se producirá
# cuando se alcance el momento definido.
programador.run()
print('PROGRAMADOR FINALIZADO:', int(time.time()))

'''
Salida:

PROGRAMADOR INICIADO: 1489659568
TIEMPO: 1489659569
Abriendo compuertas...
TIEMPO: 1489659573
Cerrando compuertas...
PROGRAMADOR FINALIZADO: 1489659573
'''


Programar eventos con posibilidad de cancelación


A continuación, un ejemplo que declara un programador con dos eventos de las mismas características que en el ejemplo anterior. En este caso se utiliza el valor de retorno del método enterabs() para cancelar el segundo evento cuando el número aleatorio devuelto por la función randint() es impar.

import sched
import time
import random

# Función que es llamada por los eventos
def abrir_cerrar(estado, mantener):
    print('TIEMPO:', int(time.time()))
    if mantener and not programador.empty():
        programador.cancel(ev2)
        
    if estado:
        print("Abriendo compuertas...")
    else:
        print("Cerrando compuertas...")
    
# Obtener un número aleatorio entre 1 y 5
# Si el valor es impar: no cerrar compuertas
valor = random.randint(1, 5)
if valor % 2:
    print("No mantener compuertas abiertas")
    mantener_abiertas = False
else:
    print("Mantener compuertas abiertas")   
    mantener_abiertas = True

# Declarar el programador
programador = sched.scheduler(time.time, time.sleep)

# Asignar el tiempo de comienzo (en segundos)
comienzo = int(time.time())
t1 = comienzo + 1   # Tiempo para abrir compuertas
t2 = t1 + 4         # Tiempo para cerrar compuertas
print('PROGRAMADOR INICIADO:', comienzo)

# Definir los dos eventos indicando: momento de ejecución, 
# prioridad, función a la que se llama y valores que se
# pasan a los argumentos de la función:
ev1 = programador.enterabs(t1, 1, abrir_cerrar, 
                           (1, mantener_abiertas))
ev2 = programador.enterabs(t2, 1, abrir_cerrar, 
                           (0, mantener_abiertas))

# Poner en marcha el programador.
# El programa permanece a la espera hasta que se
# ejecuten los dos eventos. La ejecución se producirá
# cuando se alcance el momento definido.
programador.run()
print('PROGRAMADOR FINALIZADO:', int(time.time()))

'''
Salidas posibles:

1) Si el número devuelto por randint() es par

No mantener compuertas abiertas
PROGRAMADOR INICIADO: 1489659900
TIEMPO: 1489659901
Abriendo compuertas...
TIEMPO: 1489659905
Cerrando compuertas...
PROGRAMADOR FINALIZADO: 1489659905

2) Si el número devuelto por randint() es impar

Mantener compuertas abiertas
PROGRAMADOR INICIADO: 1489659972
TIEMPO: 1489659973
Abriendo compuertas...
PROGRAMADOR FINALIZADO: 1489659973

'''


Programar eventos para ejecutar después de un tiempo de espera


Seguidamente otro ejemplo con un programador con tres eventos para ejecutar la misma tarea después de esperar 1, 2 y 3 segundos, respectivamente. El programador se crea con time.monotonic() que es la función que utiliza por defecto el constructor, que devuelve el tiempo expresado en segundos. En este caso el primer argumento de la función enter() representa el valor del tiempo de espera que utiliza la función sleep() en el constructor.

import sched
import time

def tarea(nombre, comienzo):
    ahora = time.time()
    diferencia = int(ahora - comienzo)
    print('MOMENTO :', int(ahora), 
          'Diferencia:', diferencia, 'segundos', 
          'Tarea:', nombre)

programador = sched.scheduler()
comienzo = time.time()
print('COMIENZO:', comienzo)

programador.enter(1, 1, tarea, ('TAREA_1', comienzo))
programador.enter(2, 1, tarea, ('TAREA_2', comienzo))
programador.enter(3, 1, tarea, ('TAREA_3', comienzo))
programador.run()

print('FINAL   :', time.time())

'''
Salida:

COMIENZO: 1489579919.1849022
MOMENTO : 1489579920 Diferencia: 1 segundos Tarea: TAREA_1
MOMENTO : 1489579921 Diferencia: 2 segundos Tarea: TAREA_2
MOMENTO : 1489579922 Diferencia: 3 segundos Tarea: TAREA_3
FINAL   : 1489579922.1852293
'''


Programar eventos para ejecutar considerando la prioridad


En el siguiente ejemplo se programan tres eventos para ejecutar la misma tarea. Uno de ellos, la ejecuta después de 1 segundo de espera y los otros dos eventos como tienen el mismo tiempo de espera (3 segundos) utilizan el argumento de la prioridad para establecer el orden de ejecución. En este caso el tercero que tiene una prioridad más alta se ejecuta antes que el segundo.

El programador se crea con la función time.time() y la función tiempo() se utiliza para 'humanizar' la salida del tiempo que se expresa en esta ocasión con el siguiente formato de fecha/hora: '%d-%m-%Y %H:%M:%S'.

import sched
import time

def tiempo():
    formato = '%d-%m-%Y %H:%M:%S'
    
    # Obtiene fecha/hora local como tupla struct_time    
    st_tiempo = time.localtime()
    
    # Convierte fecha/hora a segundos
    tiempo = time.mktime(st_tiempo)
    
    # Convierte fecha/hora a cadena
    str_tiempo = time.strftime(formato, st_tiempo)
    return tiempo, str_tiempo    

def tarea(nombre, comienzo):
    ahora, str_ahora = tiempo()
    diferencia = int(ahora - comienzo)
    print('MOMENTO :', str_ahora, 
          'Diferencia:', diferencia, 'segundos', 
          'Tarea:', nombre)

programador = sched.scheduler(time.time, time.sleep)
comienzo, str_comienzo = tiempo()
print('COMIENZO:', str_comienzo)

programador.enter(1, 1, tarea, ('TAREA_1', comienzo))
programador.enter(3, 2, tarea, ('TAREA_2', comienzo))
programador.enter(3, 1, tarea, ('TAREA_3', comienzo))
programador.run()

final, str_final = tiempo()
print('FINAL   :', str_final)

'''
Salida:

COMIENZO: 15-03-2017 11:36:07
MOMENTO : 15-03-2017 11:36:08 Diferencia: 1 segundos Tarea: TAREA_1
MOMENTO : 15-03-2017 11:36:10 Diferencia: 3 segundos Tarea: TAREA_3
MOMENTO : 15-03-2017 11:36:10 Diferencia: 3 segundos Tarea: TAREA_2
FINAL   : 15-03-2017 11:36:10
'''


Programar eventos para ejecutar controlando el bloqueo


Para finalizar, un ejemplo en el que se utiliza el argumento blocking del método run() para bloquear el programa para que espere a que finalice la ejecución de todos los eventos o, bien, para que se ejecuten sólo los eventos de ejecución inmediata (son aquellos que tienen el valor 0 en el tiempo de espera.

En este caso se utiliza también la función randint() para generar un número aleatorio. Si el número obtenido es par blocking toma el valor True y el programa espera la ejecución de todos los eventos. Si el número es impar sólo se ejecutan los eventos inmediatos.

El programador se crea con la función time.time() y la función tiempo() se utiliza para 'humanizar' la salida del tiempo que se expresa en esta ocasión con el siguiente formato de fecha/hora: '%d-%m-%Y %H:%M:%S'.

import sched
import time
import random

def tiempo():
    formato = '%d-%m-%Y %H:%M:%S'
    
    # Obtiene fecha/hora local como tupla struct_time    
    st_tiempo = time.localtime()
    
    # Convierte fecha/hora a segundos
    tiempo = time.mktime(st_tiempo)
    
    # Convierte fecha/hora a cadena
    str_tiempo = time.strftime(formato, st_tiempo)
    return tiempo, str_tiempo    

def tarea(nombre, comienzo):
    ahora, str_ahora = tiempo()
    diferencia = int(ahora - comienzo)
    print('MOMENTO :', str_ahora, 
          'Diferencia:', diferencia, 'segundos', 
          'Tarea:', nombre)

# Obtener un número aleatorio entre 1 y 5
# Si el valor es impar: ejecutar todos los eventos
valor = random.randint(1, 5)
if valor % 2:
    print("Ejecutar todos los eventos")
    bloquear = True
else:
    print("Ejecutar sólo eventos inmediatos")   
    bloquear = False

programador = sched.scheduler(time.time, time.sleep)
comienzo, str_comienzo = tiempo()
print('COMIENZO:', str_comienzo)

programador.enter(0, 1, tarea, ('TAREA_1', comienzo))
programador.enter(0, 1, tarea, ('TAREA_2', comienzo))
programador.enter(3, 1, tarea, ('TAREA_3', comienzo))
programador.run(blocking=bloquear)

final, str_final = tiempo()
print('FINAL   :', str_final)

'''
Salidas posibles:

1) Si el número devuelto por randint() es impar:

Ejecutar todos los eventos
COMIENZO: 16-03-2017 12:13:44
MOMENTO : 16-03-2017 12:13:44 Diferencia: 0 segundos Tarea: TAREA_1
MOMENTO : 16-03-2017 12:13:44 Diferencia: 0 segundos Tarea: TAREA_2
MOMENTO : 16-03-2017 12:13:47 Diferencia: 3 segundos Tarea: TAREA_3
FINAL   : 16-03-2017 12:13:47

2) Si el número devuelto por randint() es par:

Ejecutar sólo eventos inmediatos
COMIENZO: 16-03-2017 12:14:11
MOMENTO : 16-03-2017 12:14:11 Diferencia: 0 segundos Tarea: TAREA_1
MOMENTO : 16-03-2017 12:14:11 Diferencia: 0 segundos Tarea: TAREA_2
FINAL   : 16-03-2017 12:14:11
'''



Ir al índice del tutorial de Python

domingo, 5 de marzo de 2017

El módulo time




El módulo time de la biblioteca estándar de Python proporciona un conjunto de funciones para trabajar con fechas y/o horas. Además de estás funciones hay otras relacionadas en los módulos datetime y calendar que conviene conocer.

En el módulo time hay funciones para obtener la fecha y/o hora de distintos tipos de relojes (incluido el reloj que ofrece el tiempo civil); para obtener información relacionada con nuestro huso horario; para convertir fechas y/o horas entre distintos tipos; para validar y aplicar formatos de fechas y/o horas y para detener durante un tiempo la ejecución de un programa.

Una fecha y/o hora específica puede estar expresada de tres modos diferentes:
  • como un número (float),
  • como una cadena 
  • y como un objeto struct_time (es una tupla a medida (namedtuple) con nueve valores enteros).

Por ejemplo, la fecha-hora 4-3-2017 19:10:01 representada en los tipos anteriores:
  • como un número: 1488651001.7188754 segundos
  • como una cadena: "Sat Mar 4 19:10:01 2017"
  • y como un objeto struct_time: time.struct_time(tm_year=2017, tm_mon=3, tm_mday=4, tm_hour=18, tm_min=10, tm_sec=1, tm_wday=5, tm_yday=63, tm_isdst=0)

Algunas funciones del módulo time operan sólo en un rango temporal denominado época que comienza el día 1 de enero de 1970 a las 0 horas y que  finaliza en el año 2038 (en un sistema de 32 bit).

El tiempo civil se puede expresar como tiempo local, o bien, como tiempo UTC que es el Tiempo Universal Coordinado para/por todos los países. La diferencia entre ambos tiempos depende del punto geográfico (que se corresponde con un huso horario de 24 existentes) y de los ajustes que algunos países aplican en el horario de verano.


El tiempo UTC se mantiene utilizando unos 400 relojes atómicos altamente precisos combinados con el tiempo de la rotación de la Tierra (Tiempo Solar).

Normalmente, para mantener la hora exacta un sistema informático sincroniza su reloj (el que mantiene la hora civil) con un servidor de tiempo dedicado de Internet que hace lo propio con alguno de los relojes atómicos que hay repartidos por todo el planeta.

Todos los husos horarios se definen en relación al tiempo UTC cuyo huso horario se sitúa centrado sobre el Meridiano Cero o Meridiano de Greenwich. Como la Tierra gira de oeste a este, al pasar de un huso horario al siguiente en dirección este se suma una hora; y si es en dirección opuesta se resta una hora, hasta alcanzar el meridiano situado a 180º que es el que marca el cambio de día.


Funciones para obtener y convertir tiempo (civil)


Obtener tiempo local (fecha-hora) en segundos: time()


import time
tiempo_segundos = time.time()
print(tiempo_segundos)  # 1488651001.7188754 segundos

# El valor obtenido se corresponde con los segundos
# que han transcurrido desde el comienzo de la 
# época: 1 de enero de 1970, 0 horas


Convertir a cadena tiempo en segundos: ctime()


tiempo_cadena = time.ctime(tiempo_segundos) # 1488651001.7188754 seg
print(tiempo_cadena)  # Sat Mar  4 19:10:01 2017

# Operando con tiempos

tiempo_segundos = 1488651001.7188754
tiempo_segundos = tiempo_segundos + 86400  # Suma 1 día
print(time.ctime(semana))  # Sun Mar  5 19:10:01 2017


Obtener tiempo UTC a partir de segundos: gmtime()


tiempo_st = time.gmtime(tiempo_segundos)
print(tiempo_st)  

# time.struct_time(tm_year=2017, tm_mon=3, tm_mday=4, 
#                  tm_hour=18, tm_min=10, tm_sec=1, 
#                  tm_wday=5, tm_yday=63, tm_isdst=0)

# El valor devuelto es un objeto struct_time con
# los siguientes atributos:

# - tm_year: año 
# - tm_mon: mes 
# - tm_mday: día del mes 
# - tm_hour: hora 
# - tm_min: minutos
# - tm_sec: segundos 
# - tm_wday: día de la semana [0, 6] 
# - tm_yday: día del año [1, 366] 
# - tm_isdst: horario de verano: 0 (no vigente), 
#                                1 (vigente) y 
#                               -1 (desconocido)

# En este caso por tratarse de la zona horaria de 
# España (península) el tiempo devuelto en UTC 
# tiene una hora menos que el tiempo local. Con el
# ajuste del horario de verano esa diferencia se 
# amplia una hora más.

# Para acceder a los valores de los atributos de 
# un objeto struct_time:

print(tiempo.st_year)  # 2017
print(tiempo.st_mon)  # 3
print(tiempo.st_hour)  # 18

# El módulo calendar tiene la función timegm() que
# es la inversa de gmtime(). 

# Si el argumento es 0 segundo, el valor que se 
# obtiene representa el momento de comienzo de la
# época: 1 de enero de 1970, 0 horas.  

print(time.gmtime(0))

# time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1,
#                  tm_hour=0, tm_min=0, tm_sec=0, 
#                  tm_wday=3, tm_yday=1, tm_isdst=0)


Obtener tiempo local como objeto struct_time: localtime()


# El tiempo devuelto por localtime() es equivalente
# a time() pero expresado como un objeto struct_time:

print('time():', time.ctime(time.time()), 
      '\nlocaltime():', time.localtime())

# time(): Sat Mar  4 20:03:25 2017 
# localtime(): time.struct_time(tm_year=2017, tm_mon=3, tm_mday=4,
#                               tm_hour=20, tm_min=3, tm_sec=25, 
#                               tm_wday=5, tm_yday=63, tm_isdst=0)


Convertir a cadena un objeto struct_time: asctime()


tiempo_st = time.localtime()
print(time.asctime(tiempo_st))  # Sat Mar  4 20:22:13 2017



Convertir a segundos un objeto struct_time: mktime()


tiempo_st = time.localtime()
print(time.mktime(tiempo_st))  #  1488655494.0



Funciones para validar y aplicar formatos


Validar tiempo expresado como cadena: strptime()


# El tiempo se expresa como una cadena en el 
# formato elegido y el valor que devuelve 
# strptime() es un objeto struct_time:

import time
tiempo_st = time.strptime("4 Mar 2017", "%d %b %Y") 
print(tiempo_st)

# time.struct_time(tm_year=2017, tm_mon=3, tm_mday=4, 
#                  tm_hour=0, tm_min=0, tm_sec=0, 
#                  tm_wday=5, tm_yday=63, tm_isdst=-1)


# Validando una fecha con el formato: dd-mm-aaaa
# Se produce una excepción porque la fecha es incorrecta

try:
    tiempo_st = time.strptime("34-03-2017", "%d-%m-%Y") 

except:
    print('Fecha incorrecta')

# El formato es opcional. Si no se indica se 
# utilizará: '%a %b %d %H:%M:%S %Y'


Convertir y formatear un objeto struct_time: strftime()


# El tiempo se expresa como objeto struct_time y 
# el valor que devuelve la función strftime() es
# una cadena con el formato elegido:

tiempo_cadena = time.strftime("%d-%m-%Y %H:%M", time.gmtime())
print(tiempo_cadena)  #  04-03-2017 20:41



Funciones y variables para obtener información del huso horario


Los valores que se obtienen en los ejemplos siguientes son del huso horario de España (península). En las Islas Canarias los relojes marcan una hora menos que en la Península, es decir, el Tiempo UTC y el Tiempo Local son coincidentes con el horario de invierno vigente.



# Obtener desplazamiento del huso local en verano.
# Diferencia en segundos entre Tiempo UTC y Tiempo
# Local con el horario de verano:

time.altzone  # -7200 segundos (-2 horas)

# Obtener desplazamiento del huso local.
# Diferencia en segundos entre Tiempo UTC y Tiempo
# Local con el horario local:

time.timezone()  # -3600 segundos (-1 hora)

# Obtener huso horario local ordinario y/o de verano
# Devuelve tupla con uno o dos cadenas dependiendo de 
# si hay ajuste en el horario de verano.

time.tzname  # En España:('CET', 'CEST')

# La función time.tzset() permite cambiar el huso
# horario de referencia

# Conocer si en el huso horaria actual se aplica ajuste en
# horario de verano: (1 , 0, -1)

time.daylight  # 1 (vigente)


Funciones para obtener tiempo del sistema


Un equipo informático utiliza varios relojes para su funcionamiento y un programa Python con algunas funciones del módulo time puede obtener y utilizar el tiempo que marcan en un momento dado.

# Obtener tiempo en segundos de reloj monotónico
# Este tipo de relojes no pueden retroceder en el
# tiempo: siempre una llamada posterior a la función
# obtendrá un valor superior que una anterior.

time.monotonic()  # 5843.732800711
time.monotonic()  # 5847.289432529

# Obtener tiempo en segundos del contador de rendimiento
# El valor que se obtiene en este tipo de relojes es
# muy preciso. Se suele utilizar para medir tiempos
# cortos.

time.perf_counter()  # 6134.798769373

# Obtener tiempo de procesamiento en segundos
# Se obtiene sumando el tiempo de CPU y del proceso actual
# del usuario.

time.process_time()  # 8.249604247                 

# Obtener información sobre un tipo de reloj:
# 'monotonic', 'perf_counter', 'process_time' y 'time'

time.get_clock_info('time')  

# namespace(adjustable=True, 
#           implementation='clock_gettime(CLOCK_REALTIME)', 
#           monotonic=False, 
#           resolution=1e-09)


Función para detener un programa: sleep()


# La función sleep() permite detener la ejecución
# de un programa durante un tiempo. 

# Se detiene cinco veces con un tiempo de parada 
# que va aumentando cada vez un segundo:

for segundos in range(1,6):
    print('Detenido durante', segundos, 'segundos')
    time.sleep(segundos)
print('Proceso finalizado')



Relacionado:

Ir al índice del tutorial de Python