web-dev-qa-db-fra.com

Planifier un événement répétitif dans Python 3

J'essaie de planifier un événement répétitif pour qu'il s'exécute chaque minute dans Python 3.

J'ai vu la classe sched.scheduler mais je me demande s'il existe un autre moyen de le faire. J'ai entendu dire que je pourrais utiliser plusieurs threads pour cela, ce qui ne me dérangerait pas.

En gros, je demande du JSON, puis je l’analyse; sa valeur change avec le temps.

Pour utiliser sched.scheduler, je dois créer une boucle pour lui demander de planifier l'exécution de l'événement pour une heure:

scheduler = sched.scheduler(time.time, time.sleep)

# Schedule the event. THIS IS UGLY!
for i in range(60):
    scheduler.enter(3600 * i, 1, query_rate_limit, ())

scheduler.run()

Quelles sont les autres façons de faire cela?

27
Humphrey Bogart

Vous pouvez utiliser threading.Timer , mais cela permet également de planifier un événement unique, de la même manière que la méthode .enter des objets du planificateur.

Le modèle normal (dans n’importe quel langage) pour transformer un planificateur ponctuel en un planificateur périodique consiste à demander à chaque événement de se planifier à nouveau à l’intervalle spécifié. Par exemple, avec sched, je n’utiliserais pas une boucle comme vous le faites, mais plutôt quelque chose comme:

def periodic(scheduler, interval, action, actionargs=()):
    scheduler.enter(interval, 1, periodic,
                    (scheduler, interval, action, actionargs))
    action(*actionargs)

et initier l'ensemble "calendrier périodique pour toujours" avec un appel

periodic(scheduler, 3600, query_rate_limit)

Ou bien, je pourrais utiliser threading.Timer au lieu de scheduler.enter, mais le motif est assez similaire.

Si vous avez besoin d’une variante plus précise (par exemple, arrêtez la reprogrammation périodique à un moment donné ou à certaines conditions), ce n’est pas trop difficile à gérer avec quelques paramètres supplémentaires.

36
Alex Martelli

Mon humble prise sur le sujet:

from threading import Timer

class RepeatedTimer(object):
    def __init__(self, interval, function, *args, **kwargs):
        self._timer     = None
        self.function   = function
        self.interval   = interval
        self.args       = args
        self.kwargs     = kwargs
        self.is_running = False
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False

Usage:

from time import sleep

def hello(name):
    print "Hello %s!" % name

print "starting..."
rt = RepeatedTimer(1, hello, "World") # it auto-starts, no need of rt.start()
try:
    sleep(5) # your long-running job goes here...
finally:
    rt.stop() # better in a try/finally block to make sure the program ends!

Caractéristiques:

  • Bibliothèque standard uniquement, pas de dépendances externes
  • Utilise le motif suggéré par Alex Martnelli
  • start() et stop() peuvent appeler plusieurs fois même si le chronomètre a déjà démarré/arrêté
  • la fonction à appeler peut avoir des arguments positionnels et nommés
  • Vous pouvez changer interval à tout moment, il sera effectif après la prochaine exécution. Idem pour args, kwargs et même function!
18
MestreLion

Vous pouvez utiliser schedule . Cela fonctionne sur Python 2.7 et 3.3 et est plutôt léger:

import schedule
import time

def job():
   print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)

while 1:
   schedule.run_pending()
   time.sleep(1)
13
dbader

Basé sur la réponse de MestreLion, il résout un petit problème avec le multithreading: 

from threading import Timer, Lock


class Periodic(object):
    """
    A periodic task running in threading.Timers
    """

    def __init__(self, interval, function, *args, **kwargs):
        self._lock = Lock()
        self._timer = None
        self.function = function
        self.interval = interval
        self.args = args
        self.kwargs = kwargs
        self._stopped = True
        if kwargs.pop('autostart', True):
            self.start()

    def start(self, from_run=False):
        self._lock.acquire()
        if from_run or self._stopped:
            self._stopped = False
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self._lock.release()

    def _run(self):
        self.start(from_run=True)
        self.function(*self.args, **self.kwargs)

    def stop(self):
        self._lock.acquire()
        self._stopped = True
        self._timer.cancel()
        self._lock.release()
6
fdb

Vous pouvez utiliser le Advanced Python Scheduler . Il a même une interface semblable à celle de cron.

6
jordixou

Utilisez Céleri .

from celery.task import PeriodicTask
from datetime import timedelta


class ProcessClicksTask(PeriodicTask):
    run_every = timedelta(minutes=30)

    def run(self, **kwargs):
        #do something
5
user

Voici une boucle rapide et peu bloquante avec Thread:

#!/usr/bin/env python3
import threading,time

def worker():
    print(time.time())
    time.sleep(5)
    t = threading.Thread(target=worker)
    t.start()


threads = []
t = threading.Thread(target=worker)
threads.append(t)
t.start()
time.sleep(7)
print("Hello World")

Il n'y a rien de particulièrement spécial, la worker crée un nouveau thread avec un délai. Peut-être pas le plus efficace, mais assez simple. La réponse de northtree serait la voie à suivre si vous avez besoin d'une solution plus sophistiquée.

Et sur la base de this , nous pouvons faire la même chose, avec Timer:

#!/usr/bin/env python3
import threading,time

def hello():
    t = threading.Timer(10.0, hello)
    t.start()
    print( "hello, world",time.time() )

t = threading.Timer(10.0, hello)
t.start()
time.sleep(12)
print("Oh,hai",time.time())
time.sleep(4)
print("How's it going?",time.time())
1

Sur la base de la réponse d'Alex Martelli, j'ai implémenté la version decorator qui est plus facile à intégrer.

import sched
import time
import datetime
from functools import wraps
from threading import Thread


def async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target=func, args=args, kwargs=kwargs)
        func_hl.start()
        return func_hl
    return async_func


def schedule(interval):
    def decorator(func):
        def periodic(scheduler, interval, action, actionargs=()):
            scheduler.enter(interval, 1, periodic,
                            (scheduler, interval, action, actionargs))
            action(*actionargs)

        @wraps(func)
        def wrap(*args, **kwargs):
            scheduler = sched.scheduler(time.time, time.sleep)
            periodic(scheduler, interval, func)
            scheduler.run()
        return wrap
    return decorator


@async
@schedule(1)
def periodic_event():
    print(datetime.datetime.now())


if __== '__main__':
    print('start')
    periodic_event()
    print('end')
1
northtree

Voir mon échantillon

import sched, time

def myTask(m,n):
  print n+' '+m

def periodic_queue(interval,func,args=(),priority=1):
  s = sched.scheduler(time.time, time.sleep)
  periodic_task(s,interval,func,args,priority)
  s.run()

def periodic_task(scheduler,interval,func,args,priority):
  func(*args)
  scheduler.enter(interval,priority,periodic_task,
                   (scheduler,interval,func,args,priority))

periodic_queue(1,myTask,('world','hello'))
0
Vladimir Avdeev