I want to repeatedly execute a function in Python every 60 seconds forever (just like an NSTimer in Objective C). This code will run as a daemon and is effectively like calling the python script every minute using a cron, but without requiring that to be set up by the user.
while True: # Code executed here time.sleep(60)
Are there any foreseeable problems with this code?
Use the sched module, which implements a general purpose event scheduler.
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()
Just lock your time loop to the system clock. Easy.
import time starttime=time.time() while True: print "tick" time.sleep(60.0 - ((time.time() - starttime) % 60.0))
from twisted.internet import task from twisted.internet import 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()
While “while True: sleep(60)” will probably work Twisted probably already implements many of the features that you will eventually need (daemonization, logging or exception handling as pointed out by bobince) and will probably be a more robust solution
The easier way I believe to be:
import time def executeSomething(): #code here time.sleep(60) while True: executeSomething()
This way your code is executed, then it waits 60 seconds then it executes again, waits, execute, etc…
No need to complicate things 😀
If you want a non-blocking way to execute your function periodically, instead of a blocking infinite loop I’d use a threaded timer. This way your code can keep running and perform other tasks and still have your function called every n seconds. I use this technique a lot for printing progress info on long, CPU/Disk/Network intensive tasks.
Here’s the code I’ve posted in a similar question, with start() and stop() control:
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
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!
- Standard library only, no external dependencies
stop()are safe to call multiple times even if the timer has already started/stopped
- function to be called can have positional and named arguments
- You can change
intervalanytime, it will be effective after next run. Same for
Here’s an update to the code from MestreLion that avoids drifiting over 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
I faced a similar problem some time back. May be http://cronus.readthedocs.org might help?
For v0.2, the following snippet works
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
The main difference between that and cron is that an exception will kill the daemon for good. You might want to wrap with an exception catcher and logger.
One possible answer:
import time t=time.time() while True: if time.time()-t>10: #run your task here t=time.time()
I use this to cause 60 events per hour with most events occurring at the same number of seconds after the whole minute:
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()
Depending upon actual conditions you might get ticks of length:
but at the end of 60 minutes you’ll have 60 ticks; and most of them will occur at the correct offset to the minute you prefer.
On my system I get typical drift of < 1/20th of a second until need for correction arises.
The advantage of this method is resolution of clock drift; which can cause issues if you’re doing things like appending one item per tick and you expect 60 items appended per hour. Failure to account for drift can cause secondary indications like moving averages to consider data too deep into the past resulting in faulty output.
e.g., Display current local time
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()) # call every minute glib.timeout_add(60*1000, display_local_time)