it-swarm.com.de

Was ist der beste Weg, eine Funktion alle x Sekunden in Python wiederholt auszuführen?

Ich möchte immer wieder alle 60 Sekunden eine Funktion in Python ausführen (genau wie ein NSTimer in Objective C). Dieser Code wird als Dämon ausgeführt und ist praktisch so, als würde er das Python-Skript jede Minute mit einem cron aufrufen, ohne dass dies vom Benutzer eingerichtet werden muss.

In diese Frage nach einem in Python implementierten cron , scheint die Lösung effektiv nur sleep () für x Sekunden zu sein. Ich brauche keine solche erweiterte Funktionalität, daher könnte so etwas funktionieren

while True:
    # Code executed here
    time.sleep(60)

Gibt es vorhersehbare Probleme mit diesem Code?

191
DavidM

Verwenden Sie das Modul sched , das einen allgemeinen Ereignisplaner implementiert.

import sched, time
s = sched.scheduler(time.time, time.sleep)
def do_something(sc): 
    print "Doing stuff..."
    # do your stuff
    s.enter(60, 1, do_something, (sc,))

s.enter(60, 1, do_something, (s,))
s.run()
176
nosklo

Sperren Sie einfach Ihre Zeitschleife an die Systemuhr. Einfach.

import time
starttime=time.time()
while True:
  print "tick"
  time.sleep(60.0 - ((time.time() - starttime) % 60.0))
120
Dave Rove

Vielleicht möchten Sie Twisted betrachten, eine Python-Netzwerkbibliothek, die das Reactor Pattern implementiert.

from twisted.internet import task, reactor

timeout = 60.0 # Sixty seconds

def doWork():
    #do work here
    pass

l = task.LoopingCall(doWork)
l.start(timeout) # call every sixty seconds

reactor.run()

Während "while True: sleep (60)" wahrscheinlich funktionieren wird Twisted implementiert wahrscheinlich bereits viele der Funktionen, die Sie eventuell benötigen (Dämonisierung, Protokollierung oder Ausnahmebehandlung, wie von Bobince dargelegt) und wird wahrscheinlich eine robustere Lösung sein

57
Aaron Maenpaa

Wenn Sie möchten, dass Ihre Funktion auf eine nicht blockierende Weise regelmäßig ausgeführt wird, würde ich anstelle einer blockierenden Endlosschleife einen Thread-Timer verwenden. Auf diese Weise kann Ihr Code weiter ausgeführt werden, andere Aufgaben ausführen und Ihre Funktion wird weiterhin alle n Sekunden aufgerufen. Ich verwende diese Technik häufig zum Drucken von Fortschrittsinformationen für lange, CPU/Disk/Netzwerk-intensive Aufgaben.

Hier ist der Code, den ich in einer ähnlichen Frage mit start () und stop () geschrieben habe:

from threading import Timer

class RepeatedTimer(object):
    def __init__(self, interval, function, *args, **kwargs):
        self._timer     = None
        self.interval   = interval
        self.function   = function
        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

Verwendungszweck:

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!

Eigenschaften:

  • Nur Standardbibliothek, keine externen Abhängigkeiten
  • start() und stop() können mehrmals sicher aufgerufen werden, auch wenn der Timer bereits gestartet/gestoppt wurde
  • die aufzurufende Funktion kann positionelle und benannte Argumente haben
  • Sie können interval jederzeit ändern, sie wird nach dem nächsten Lauf wirksam. Gleiches für args, kwargs und sogar function!
41
MestreLion

Der einfachere Weg glaube ich zu sein:

import time

def executeSomething():
    #code here
    time.sleep(60)

while True:
    executeSomething()

Auf diese Weise wird Ihr Code ausgeführt, dann wartet er 60 Sekunden, dann wird er erneut ausgeführt, gewartet, ausgeführt usw. .... __ Keine Notwendigkeit, die Dinge zu komplizieren: D

30
Itxaka

Hier ist ein Update des Codes von MestreLion, mit dem das Driften in der Zeit vermieden wird:

import threading 
import time

class RepeatedTimer(object):
  def __init__(self, interval, function, *args, **kwargs):
    self._timer = None
    self.interval = interval
    self.function = function
    self.args = args
    self.kwargs = kwargs
    self.is_running = False
    self.next_call = time.time()
    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.next_call += self.interval
      self._timer = threading.Timer(self.next_call - time.time(), self._run)
      self._timer.start()
      self.is_running = True

  def stop(self):
    self._timer.cancel()
    self.is_running = False
14
eraoul
import time, traceback

def every(delay, task):
  next_time = time.time() + delay
  while True:
    time.sleep(max(0, next_time - time.time()))
    try:
      task()
    except Exception:
      traceback.print_exc()
      # in production code you might want to have this instead of course:
      # logger.exception("Problem while executing repetitive task.")
    # skip tasks if we are behind schedule:
    next_time += (time.time() - next_time) // delay * delay + delay

def foo():
  print("foo", time.time())

every(5, foo)

Wenn Sie dies tun möchten, ohne den verbleibenden Code zu blockieren, können Sie dies so verwenden, dass er in einem eigenen Thread ausgeführt wird:

import threading
threading.Thread(target=lambda: every(5, foo)).start()

Diese Lösung kombiniert mehrere Funktionen, die selten in den anderen Lösungen gefunden wurden:

  • Ausnahmebehandlung: Ausnahmen werden, soweit dies auf dieser Ebene möglich ist, ordnungsgemäß behandelt, d. e. loggen Sie sich zu Debugging-Zwecken ein, ohne unser Programm abzubrechen.
  • Keine Verkettung: Die allgemeine kettenähnliche Implementierung (zum Planen des nächsten Ereignisses), die Sie in vielen Antworten finden, ist brüchig in dem Aspekt, dass, wenn etwas innerhalb des Planungsmechanismus (threading.Timer oder was auch immer) schief geht, dies die Kette beendet . Es werden dann keine weiteren Ausführungen ausgeführt, auch wenn der Grund des Problems bereits behoben ist. Eine einfache Schleife und Warten mit einer einfachen sleep() ist im Vergleich viel robuster.
  • Keine Abweichung: Meine Lösung verfolgt genau die Zeiten, zu denen sie laufen soll. Abhängig von der Ausführungszeit gibt es keine Abweichung (wie bei vielen anderen Lösungen).
  • Überspringen: Meine Lösung überspringt Aufgaben, wenn eine Ausführung zu viel Zeit in Anspruch nahm (z. B. X alle fünf Sekunden, X jedoch 6 Sekunden). Dies ist das Standardverhalten von cron (und das aus gutem Grund). Viele andere Lösungen führen die Task dann einfach mehrmals hintereinander ohne Verzögerung aus. In den meisten Fällen (z. B. Bereinigungsaufgaben) ist dies nicht erwünscht. Wenn es ist, verwenden Sie stattdessen einfach next_time += delay.
10
Alfe

Ich hatte vor einiger Zeit ein ähnliches Problem. Kann sein http://cronus.readthedocs.org könnte helfen?

Für v0.2 funktioniert das folgende Snippet

import cronus.beat as beat

beat.set_rate(2) # 2 Hz
while beat.true():
    # do some time consuming work here
    beat.sleep() # total loop duration would be 0.5 sec
5
Anay

Der Hauptunterschied zwischen diesem und cron ist, dass eine Ausnahme den Dämon für immer töten wird. Sie möchten möglicherweise einen Exception-Catcher und -Logger verwenden.

4
bobince

Eine mögliche Antwort:

import time
t=time.time()

while True:
    if time.time()-t>10:
        #run your task here
        t=time.time()
1
sks

b. Aktuelle Ortszeit anzeigen

import datetime
import glib
import logger

def get_local_time():
    current_time = datetime.datetime.now().strftime("%H:%M")
    logger.info("get_local_time(): %s",current_time)
    return str(current_time)

def display_local_time():
    logger.info("Current time is: %s", get_local_time())
    return True

# call every minute
glib.timeout_add(60*1000, display_local_time)
0
rise.riyo
    ''' tracking number of times it prints'''
import threading

global timeInterval
count=0
def printit():
  threading.Timer(timeInterval, printit).start()
  print( "Hello, World!")
  global count
  count=count+1
  print(count)
printit

if __== "__main__":
    timeInterval= int(input('Enter Time in Seconds:'))
    printit()
0
raviGupta

Ich verwende die Tkinter after () - Methode, die das Spiel nicht "stiehlt" (wie das zuvor präsentierte Modul sched ), d. H., Andere Dinge können parallel ausgeführt werden:

import Tkinter

def do_something1():
  global n1
  n1 += 1
  if n1 == 6: # (Optional condition)
    print "* do_something1() is done *"; return
  # Do your stuff here
  # ...
  print "do_something1() "+str(n1)
  tk.after(1000, do_something1)

def do_something2(): 
  global n2
  n2 += 1
  if n2 == 6: # (Optional condition)
    print "* do_something2() is done *"; return
  # Do your stuff here
  # ...
  print "do_something2() "+str(n2)
  tk.after(500, do_something2)

tk = Tkinter.Tk(); 
n1 = 0; n2 = 0
do_something1()
do_something2()
tk.mainloop()

do_something1() und do_something2() können parallel und in beliebigem Intervall laufen. Hier wird der zweite doppelt so schnell ausgeführt. Beachten Sie auch, dass ich einen einfachen Zähler als Bedingung verwendet habe, um eine der beiden Funktionen zu beenden. Sie können beliebige andere Bedingungen oder keine verwenden, wenn Sie eine Funktion ausführen, bis das Programm beendet ist (z. B. eine Uhr). 

0
Apostolos

Ich benutze das, um 60 Ereignisse pro Stunde zu verursachen, wobei die meisten Ereignisse mit der gleichen Anzahl Sekunden nach der ganzen Minute auftreten:

import math
import time
import random

TICK = 60 # one minute tick size
TICK_TIMING = 59 # execute on 59th second of the tick
TICK_MINIMUM = 30 # minimum catch up tick size when lagging

def set_timing():

    now = time.time()
    elapsed = now - info['begin']
    minutes = math.floor(elapsed/TICK)
    tick_elapsed = now - info['completion_time']
    if (info['tick']+1) > minutes:
        wait = max(0,(TICK_TIMING-(time.time() % TICK)))
        print ('standard wait: %.2f' % wait)
        time.sleep(wait)
    Elif tick_elapsed < TICK_MINIMUM:
        wait = TICK_MINIMUM-tick_elapsed
        print ('minimum wait: %.2f' % wait)
        time.sleep(wait)
    else:
        print ('skip set_timing(); no wait')
    drift = ((time.time() - info['begin']) - info['tick']*TICK -
        TICK_TIMING + info['begin']%TICK)
    print ('drift: %.6f' % drift)

info['tick'] = 0
info['begin'] = time.time()
info['completion_time'] = info['begin'] - TICK

while 1:

    set_timing()

    print('hello world')

    #random real world event
    time.sleep(random.random()*TICK_MINIMUM)

    info['tick'] += 1
    info['completion_time'] = time.time()

Abhängig von den tatsächlichen Bedingungen erhalten Sie möglicherweise Längenabstände:

60,60,62,58,60,60,120,30,30,60,60,60,60,60...etc.

aber nach 60 Minuten haben Sie 60 Ticks; und die meisten davon werden im richtigen Abstand zu der von Ihnen bevorzugten Minute angezeigt.

Auf meinem System erhalte ich eine typische Drift von <1/20 einer Sekunde, bis ein Korrekturbedarf entsteht.

Der Vorteil dieser Methode ist die Auflösung der Taktabweichung; Dies kann zu Problemen führen, wenn Sie beispielsweise ein Element pro Tick anhängen und Sie erwarten, dass 60 Elemente pro Stunde angehängt werden. Wenn die Drift nicht berücksichtigt wird, können sekundäre Hinweise wie gleitende Durchschnitte dazu führen, dass Daten zu weit in die Vergangenheit zurückgehen, was zu fehlerhafter Ausgabe führt. 

0
litepresence

Hier ist eine angepasste Version für den Code von MestreLion . Zusätzlich zur Originalfunktion dieser Code:

1) add_in_interval hinzufügen, um den Timer zu einer bestimmten Zeit auszulösen (der Aufrufer muss das erste_interval berechnen und übergeben)

2) löse eine Race-Bedingung im Originalcode. Wenn der Kontrollthread im ursprünglichen Code den laufenden Timer nicht abbrechen konnte ("Stoppen Sie den Timer und brechen Sie die Ausführung der Timer-Aktion ab. Dies funktioniert nur, wenn sich der Timer noch im Wartestadium befindet.") Zitiert aus https: //docs.python.org/2/library/threading.html ) wird der Timer endlos ausgeführt.

class RepeatedTimer(object):
def __init__(self, first_interval, interval, func, *args, **kwargs):
    self.timer      = None
    self.first_interval = first_interval
    self.interval   = interval
    self.func   = func
    self.args       = args
    self.kwargs     = kwargs
    self.running = False
    self.is_started = False

def first_start(self):
    try:
        # no race-condition here because only control thread will call this method
        # if already started will not start again
        if not self.is_started:
            self.is_started = True
            self.timer = Timer(self.first_interval, self.run)
            self.running = True
            self.timer.start()
    except Exception as e:
        log_print(syslog.LOG_ERR, "timer first_start failed %s %s"%(e.message, traceback.format_exc()))
        raise

def run(self):
    # if not stopped start again
    if self.running:
        self.timer = Timer(self.interval, self.run)
        self.timer.start()
    self.func(*self.args, **self.kwargs)

def stop(self):
    # cancel current timer in case failed it's still OK
    # if already stopped doesn't matter to stop again
    if self.timer:
        self.timer.cancel()
    self.running = False
0
dproc