How to repeatedly execute a function every x seconds?
Asked Answered
D

23

499

I want to repeatedly execute a function in Python every 60 seconds forever (just like an NSTimer in Objective C or setTimeout in JS). 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.

In this question about a cron implemented in Python, the solution appears to effectively just sleep() for x seconds. I don't need such advanced functionality so perhaps something like this would work

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

Are there any foreseeable problems with this code?

Demmer answered 23/1, 2009 at 21:7 Comment(9)
A pedantic point, but may be critical, your code above code doesn't execute every 60 seconds it puts a 60 second gap between executions. It only happens every 60 seconds if your executed code takes no time at all.Conroy
Dupe: #373835Ionize
also time.sleep(60) may return both earlier and laterMalachite
I am still wondering: Are there any foreseeable problems with this code?Nanna
The "foreseeable problem" is you cannot expect 60 iterations per hour by just using time.sleep(60). So if you're appending one item per iteration and keeping a list of set length... the average of that list will not represent a consistent "period" of time; so functions such as "moving average" can be referencing data points that are too old, which will distort your indication.Hannie
See Python Scheduling for a tiny simple example.Witchy
@Banana Yes, you can expect any problems caused because your script is not executed EXACTLY every 60 seconds. For instance. I started doing something like this to split video streams and upload'em, and I ended up getting strems 5-10~ seconds longer because the media queue is buffering while I process data inside the loop. It depends on your data. If the function is some kind of simple watchdog thats warns you, for instance, when your disk is full, you should have no problems at all with this.If you're checking a nuclear power plant warning alerts you may end up with a city completly blown up xEtheline
You badly need to clarify 'best': Do you genuinely need to execute precisely every x seconds, with millisecond accuracy (< 50 ms) (which is what cron does, or @DaveRove's answer), or roughly every x seconds? (You say your code is supposed to be "effectively like cron", but do you mean the accuracy?)Crossstitch
So if they tracked the time their code snippet takes to execute, and then subtracted that from the 60 seconds each iteration and only sleep 60-code_time, would there be any reason to use other libraries?Notogaea
C
394

If your program doesn't have a event loop already, use the sched module, which implements a general purpose event scheduler.

import sched, time

def do_something(scheduler): 
    # schedule the next call first
    scheduler.enter(60, 1, do_something, (scheduler,))
    print("Doing stuff...")
    # then do your stuff

my_scheduler = sched.scheduler(time.time, time.sleep)
my_scheduler.enter(60, 1, do_something, (my_scheduler,))
my_scheduler.run()

If you're already using an event loop library like asyncio, trio, tkinter, PyQt5, gobject, kivy, and many others - just schedule the task using your existing event loop library's methods, instead.

Cymatium answered 23/1, 2009 at 21:9 Comment(27)
The sched module is for scheduling functions to run after some time, how do you use it to repeat a function call every x seconds without using time.sleep()?Graybeard
@Baishampayan: Just schedule a new run.Cymatium
Kronos, based on sched, offers a higher level interface: razorvine.net/download/kronos.py Used by TurboGears.Ionize
Then apscheduler at packages.python.org/APScheduler should also get a mention at this point.Phosphorite
The documentation of the shed module also points to the threading.Timer class, which is better suited for multithreaded environments. docs.python.org/2/library/threading.html#threading.Timer An example can be found in the sched module documentation.Phosphorite
note: this version may drift. You could use enterabs() to avoid it. Here's a non-drifting version for comparison.Malachite
@J.F.Sebastian: Why is this version can drift?Muttonhead
@JavaSa: because "do your stuff" is not instantaneous and errors from time.sleep may accumulate here. "execute every X seconds" and "execute with a delay of ~X seconds repeatedly" are not the same. See also this commentMalachite
This does not answer the question.Tybie
OK, but how does exit from this timer (loop)?Neutretto
@Neutretto it runs until there are no more scheduled events, so you can just not schedule another run, s.enter(60, 1, do_something, (sc,)) is the line that schedules a new run, just don't run that part when you want to stop the loopCymatium
If you remove this line then it's not an interval running anymore: it's just a delay of execution; it runs once and that's all. What I meant with my question is that you need to add a condition on which this loop will terminate. E.g. if ...: return This will terminate the loop. BTW, there's something else, more important: This loop, as it is used in the example, locks the program, i.e. nothing else can run until it terminates.Neutretto
@Neutretto if, inside the do_something() function, you do if ...: return without calling s.enter(...) that will terminate the loop, because nothing else will be scheduled to run. Code flow will be unlocked from s.run() call and will continue if there is code after that.Cymatium
Exactly, this is what I mean. The if ...: return condition is needed if one needs to terminate the loop. But most importantly, as I said, is that this method "locks" the flow of the program, i.e. you can't do anything after s.run() (until the loop terminates). (See the solution that I offer, further down.)Neutretto
@Neutretto the solution you provide uses Tkinter.Tk.mainloop() to do the same. In your terms: you can't do anything after mainloop() (until the loop terminates). The only difference is that you're using a UI library, while I'm using a library specifically made to schedule calls that doesn't try to create UI windows.Cymatium
I think I explained it already, but I'll give it one more try based on your last comment. The difference between the two is that while your clock is working, i.e. after issuing the s.run() command, nothing else can be run, whereas in my method, the clock starts working and you can still do whatever actions you want after that. You give the mainloop() command only when you have finished with everything you wanted to do. So the difference is really huge.Neutretto
@Neutretto huh, but then, if the clock expires before mainloop() is reached, the functions won't be called... The after() call only works when the mainloop is running, not before. You can calculate the time that passed and subtract that when scheduling the first run, if you wantCymatium
How does python handle the stacks? Wont this make a nearly infinite stack of executed by not yet finished functions due to recursivity? I need to schedule several milion functions per day.Etheline
@Etheline The scheduled function is added to a list, it is not called recusively. When the current function finishes, the scheduler looks for the next function to call. So the call stack never piles up - you won't run into recursivity issues.Cymatium
@Cymatium So if I get it right, s.enter(60, 1, do_something, (sc,)) puts the task in a list to be executes once after 60 seconds passed, and then, if the scheduler is set to run, the counter begins to clock down until zero, moment which the function get executed. If you add new elements to the list, theis clocks start ticking just after the .enter is executed. Am i right?Etheline
It seems youŕe right @Etheline the clock starts just after enter if you schedule a new runCymatium
You could move s.enter(...) to the start of the function to reduce drift. Also, what is the point of sc?Leverhulme
if you pass S as an argument to the do_something function, so you have to call SC.enter inside the function no ? or remove scHindward
adding this code to my django project made it hang on Performing system checks... stepSalters
How to use asyncio in this case?Blau
Even move scheduler. Enter() to start, the job launch still has very tiny drift (about 0.02s). But it still has latency if it run very long timeGwyngwyneth
@Etheline You can also add a print("test") after s.enter(...) and see that it is executed immediately after the other code because the s.enter(...) is not blocking and thus does not do the next scheduled call itself and so neccessarily does not wait for the terminaltion of all potentially called recursive function calls.Laurelaureano
I
372

Lock your time loop to the system clock like this:

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

Use a 'monotonic' clock to work properly; time() is adjusted by solar/legal time changes, ntp synchronization, etc...

Independent answered 11/8, 2014 at 20:25 Comment(18)
+1. yours and the twisted answer are the only answers that run a function every x seconds. The rest execute the function with a delay of x seconds after each call.Malachite
If you where to add some code to this which took longer than a single second... It would throw the timing out and start to lag behind.. The accepted answer in this case is correct... Anyone can loop a simple print command and have it run every second without delay...Morel
I prefer from time import time, sleep because of the existential implications ;)Hugohugon
@Mayhem: wrong. 1- No solution will work if the code takes longer than the period (otherwise you'll run out of resources eventually). 2- This solution may skip a tick but it always runs at the whole period boundary (a minute in this case). You can't just "loop a simple print command" here -- the purpose of the code is to avoid drift after multiple iterations.Malachite
Works fantastically. There is no need to subtract your starttime if you begin by syncing it to a certain time: time.sleep(60 - time.time() % 60) has been working fine for me. I've used it as time.sleep(1200 - time.time() % 1200) and it gives me logs on the :00 :20 :40, exactly as I wanted.Savil
@J.F.Sebastian: What is the purpose of the % 60 at the end?Illegible
@AntonSchigur to avoid drift after multiple iterations. An individual iteration may start slightly sooner or later depending on sleep(), timer() precision and how long it takes to execute the loop body but on average iterations always occur on the interval boundaries (even if some are skipped): while keep_doing_it(): sleep(interval - timer() % interval). Compare it with just while keep_doing_it(): sleep(interval) where errors may accumulate after several iterations.Malachite
@TrainHeartnet When the modulus is left out the result of time.time() - starttime will be bigger than the set time (in this case 60), so the result of 60 - (time.time() - starttime) will be negative, which causes the sleep function to freeze (not exactly, but it just waits for an enormous amount of time). The %60 in this case just prevents it from becoming bigger than 60.Farinaceous
This solution has the least amount of drift in this thread, but the disadvantage is that time.time() - starttime will become a really big number after a while. What i prefer to do is to move the starttime declaration in the loop. This is less precise but only has a noticable effect when using smaller times. -Edit: nevermind, it will never become bigger than timer.timer(), so problems will only occur if your script is running for a few hunred billion yearsFarinaceous
I've been looking for a better way than the time.time() - start_time method I've been using, this one looks accurate to 0.1 of a second, which is good enough for me.Oligosaccharide
I think starttime=time.time() should also come as first line inside the while loop.Turley
@Turley No, I believe that is the whole point (remembering the initial start and using the modulo) -- prevents drifts. I had similar code (sleep(interval - (end-start)) with startTime inside the while loop, but this solution is better I think. I adapted it, thanks.Paratroops
@smoothware, you can even remove "starttime=time.time()" and "- starttime" from "time.time() - starttime" at all. The intervals still will be equal and not drifted. Just not connected to any time point before the loop.Abbe
@Savil This will run every 60 seconds. If the function takes 28 seconds to run, time.sleep will be 32. FYI.Madelainemadeleine
See my answer below for a more flexible class- and timer-based solution that also works correctly. It also has no drift (despite the comment here that states there are only 2 solutions here that work).Eschatology
One thing to note is that if the function takes longer to run than the sleep interval, the module operator will wrap around and sleep for longer than expected. If the sleep interval is 60 seconds and the function takes 61 seconds to execute, the sleep statement will sleep for 59 seconds totalling a "tick" every 120 seconds rather than 60 (and so on for 121s, 181s etc.)Unshaped
Thanks for this. So simple and elegant. And like all great solutions, so obvious when you know it.Khalil
time.monotonic() would be more appropriate here to a avoid daylight time savings and other related issues.Hyksos
R
112

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

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!

Features:

  • Standard library only, no external dependencies
  • start() and 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 interval anytime, it will be effective after next run. Same for args, kwargs and even function!
Roana answered 11/7, 2016 at 22:15 Comment(6)
This solution seems to drift over time; I needed a version that aims to call the function every n seconds without drift. I'll post an update in a separate question.Eschatology
In def _run(self) I am trying to wrap my head around why you call self.start() before self.function(). Can you elaborate? I would think by calling start() first self.is_running would always be False so then we would always spin up a new thread.Chondrule
I think I got to the bottom of it. @MestreLion's solution runs a function every x seconds (i.e. t=0, t=1x, t=2x, t=3x, ...) where at the original posters sample code runs a function with x second interval in between. Also, this solution I believe has a bug if interval is shorter than the time it takes function to execute. In that case, self._timer will get overwritten in the start function.Chondrule
Yes, @RichieEpiscopo, the call to .function() after .start() is to run the function at t=0. And I don't think it will be a problem if function takes longer than interval, but yes there might be some racing conditions on the code.Roana
This is the only non-blocking way I could get. Thanks.Turley
@Eschatology : yes, this solution does drift, although it takes a few hundred or even a couple thousand runs before it drifts a single second, depending on your system. If such drift is relevant to you I strongly suggest using a proper system scheduler such as cronRoana
B
89

You might want to consider Twisted which is a Python networking library that implements the Reactor Pattern.

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()

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

Bundelkhand answered 23/1, 2009 at 21:14 Comment(5)
Great answer as well, very accurate without drift. I wonder if this puts the CPU to sleep as well while waiting to execute the task (a.k.a. not busy-waiting)?Paratroops
this drifts at the millisecond levelGenia
What does "drifts at the millisecond level" mean?Amorous
Is there anyway to break the loop, lets say after 10 minutes? @Aaron MaenpaaWurst
twisted is super cool but it seems like overkill for the particular problem described.Eschatology
E
54

Here's an update to the code from MestreLion that avoids drifiting over time.

The RepeatedTimer class here calls the given function every "interval" seconds as requested by the OP; the schedule doesn't depend on how long the function takes to execute. I like this solution since it doesn't have external library dependencies; this is just pure python.

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

Sample usage (copied from MestreLion's answer):

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!
Eschatology answered 5/12, 2016 at 0:22 Comment(2)
I agree this is the best - no 3rd party packages and I have tested that it doesn't drift over timeKoheleth
Note that this suffers from the same problem in that it's going to be creating threads for every single call, without any way to catch errors that occur inside those threads.Thanos
O
51
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)

If you want to do this without blocking your remaining code, you can use this to let it run in its own thread:

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

This solution combines several features rarely found combined in the other solutions:

  • Exception handling: As far as possible on this level, exceptions are handled properly, i. e. get logged for debugging purposes without aborting our program.
  • No chaining: The common chain-like implementation (for scheduling the next event) you find in many answers is brittle in the aspect that if anything goes wrong within the scheduling mechanism (threading.Timer or whatever), this will terminate the chain. No further executions will happen then, even if the reason of the problem is already fixed. A simple loop and waiting with a simple sleep() is much more robust in comparison.
  • No drift: My solution keeps an exact track of the times it is supposed to run at. There is no drift depending on the execution time (as in many other solutions).
  • Skipping: My solution will skip tasks if one execution took too much time (e. g. do X every five seconds, but X took 6 seconds). This is the standard cron behavior (and for a good reason). Many other solutions then simply execute the task several times in a row without any delay. For most cases (e. g. cleanup tasks) this is not wished. If it is wished, simply use next_time += delay instead.
Oceanic answered 12/4, 2018 at 16:31 Comment(9)
best answer for not drifting.Jason
upvoted! how do you do this without sleep, I have a redis subscriber with real time data incoming and therefore cannot afford to sleep but need to run something every minuteBlair
@Blair I would do this in a different thread. You could do it in the same thread but then you end up programming your own scheduling system which is way too complex for a comment.Oceanic
thanks for sharing my only concern was that i needed to access a variable too for reading it, reading a variable in 2 threads is a bad idea no, hence the questionBlair
In Python, thanks to the GIL, accessing variables in two threads is perfectly safe. And mere reading in two threads should never be a problem (also not in other threaded environments). Only writing from two different threads in a system without a GIL (e. g. in Java, C++, etc.) needs some explicit synchronization.Oceanic
@Alfe, would you recommend using a Lock or muliprocessing if one was to read real-time data every tot seconds while performing other tasks? I'm thinking about posting a question about it.Skippet
@Skippet Without any further information I would first approach the task from the threaded side. One thread reads the data now and then and then sleeps until it's time again to do it. The solution above could be used to do this of course. But I could imagine a bunch of reasons to go a different way. So good luck :)Oceanic
Sleep can be replace by threading.Event wait with timeout to be more responsive on application exit. #29082768Figured
Useful addition if one is looking for faster retries on failure: At the end of the except Exception block, add time.sleep(delay_after_failed_execution) and continue. This will only wait delay_after_failed_execution seconds after a failing task instead of the full amount of delay. The variable delay_after_failed_execution can be made configurable as an argument.Ohaus
V
37

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 :D

Vargas answered 4/11, 2012 at 10:26 Comment(3)
Actually this is not the answer : time sleep() can only be used for waiting X seconds after every execution. For example , if your function takes 0.5 seconds to execute and you use time.sleep(1) , it means your function executes every 1.5 seconds , not 1. You should use other modules and/or threads to make sure something works for Y times in every X second.Incensory
@kommradHomer: Dave Rove's answer demonstrates that you can use time.sleep() run something every X secondsMalachite
In my opinion the code should call time.sleep() in while True loop like: def executeSomething(): print('10 sec left') ; while True: executeSomething(); time.sleep(10)Vallation
H
24

I ended up using the schedule module. The API is nice.

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)
schedule.every(5).to(10).minutes.do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
schedule.every().minute.at(":17").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)
Hairspring answered 5/12, 2019 at 21:44 Comment(3)
I'm having a hard time trying to use this module in particular, I need to unblock the main thread, I've checked the FAQ in the schedule's documentation website, but I didn't really understand the workaround supplied. Does anyone know where I can find a working example that doesn't block the main thread?Eirena
use gevent.spawn() to have it not block your main thread. I call a method that handles all of my scheduler initialization through that and it works absolutely great.Transmissible
To have a function run every so many minutes at the beginning of the minute, the following works well: schedule.every(MIN_BETWEEN_IMAGES).minutes.at(":00").do(run_function) where MIN_BETWEEN_IMAGES is the number of minutes and run_function is the function to run.Telekinesis
L
10

Alternative flexibility solution is Apscheduler.

pip install apscheduler
from apscheduler.schedulers.background import BlockingScheduler
def print_t():
  pass

sched = BlockingScheduler()
sched.add_job(print_t, 'interval', seconds =60) #will do the print_t work for every 60 seconds

sched.start()

Also, apscheduler provides so many schedulers as follow.

  • BlockingScheduler: use when the scheduler is the only thing running in your process

  • BackgroundScheduler: use when you’re not using any of the frameworks below, and want the scheduler to run in the background inside your application

  • AsyncIOScheduler: use if your application uses the asyncio module

  • GeventScheduler: use if your application uses gevent

  • TornadoScheduler: use if you’re building a Tornado application

  • TwistedScheduler: use if you’re building a Twisted application

  • QtScheduler: use if you’re building a Qt application

Lunneta answered 30/11, 2020 at 5:18 Comment(1)
Works like a charm, but a PytzUsageWarning gets thrown asking the user to migrate to a new time zone provider, as pytz is deprecated because it's not PEP 495-compatible. That's a bit of a shame.Appeal
B
7

If drift is not a concern

import threading, time

def print_every_n_seconds(n=2):
    while True:
        print(time.ctime())
        time.sleep(n)
    
thread = threading.Thread(target=print_every_n_seconds, daemon=True)
thread.start()

Which asynchronously outputs.

#Tue Oct 16 17:29:40 2018
#Tue Oct 16 17:29:42 2018
#Tue Oct 16 17:29:44 2018

If the task being run takes appreciable amount of time, then the interval becomes 2 seconds + task time, so if you need precise scheduling then this is not for you.

Note the daemon=True flag means this thread won't block the app from shutting down. For example, had issue where pytest would hang indefinitely after running tests waiting for this thead to cease.

Brakesman answered 21/5, 2020 at 15:19 Comment(7)
No, it prints only the first datetime and then stops...Steviestevy
Are you sure - I just copy and pasted in terminal. It returns right away but the printout continues in background for me.Brakesman
It looks like I am missing something here. I copy/pasted the code in test.py, and run with python test.py. With Python2.7 I need to remove daemon=True that's not recognized and I read multiple prints. With Python3.8 it stops after the first print and no process is active after its end. Removing daemon=True I read multiple prints...Steviestevy
hmm strange - I am on python 3.6.10 but don't know why that would matterBrakesman
Again: Python3.4.2 (Debian GNU/Linux 8 (jessie)), had to remove daemon=True so it can multiple-print. With daemon I get a syntax error. The previous tests with Python2.7 and 3.8 were on Ubuntu 19.10 Could it be that daemon is treated differently according to the OS?Steviestevy
This drifts over time; the sleep only happens after the function's work is done. The OP may expect a more reliable schedule that starts every n seconds.Eschatology
@Eschatology I know, my answer does mention that. I've boldened that portion so it stands out better.Brakesman
N
6

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) # run twice per second
while beat.true():
    # do some time consuming work here
    beat.sleep() # total loop duration would be 0.5 sec
Nonparous answered 6/6, 2014 at 18:21 Comment(0)
F
5

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.

Frivolous answered 23/1, 2009 at 21:12 Comment(0)
U
5

Simply use

import time

while True:
    print("this will run after every 30 sec")
    #Your code here
    time.sleep(30)
Unhitch answered 11/5, 2021 at 9:45 Comment(1)
this blocks the entire thread executionCallant
G
2

One possible answer:

import time
t=time.time()

while True:
    if time.time()-t>10:
        #run your task here
        t=time.time()
Gallegos answered 24/3, 2017 at 6:56 Comment(3)
This is busy waiting an therefore very bad.Oceanic
Good solution for someone looking for a non blocking timer.Kickapoo
This is a busy wait. That means the computer will loop as fast as possible on the while True: loop consuming all possible CPU time for a single thread. It is very rare that this is a good solution.Crosscrosslet
N
2

I use Tkinter after() method, which doesn't "steal the game" (like the sched module that was presented earlier), i.e. it allows other things to run in parallel:

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() and do_something2() can run in parallel and in whatever interval speed. Here, the 2nd one will be executed twice as fast.Note also that I have used a simple counter as a condition to terminate either function. You can use whatever other contition you like or none if you what a function to run until the program terminates (e.g. a clock).

Neutretto answered 14/3, 2018 at 8:27 Comment(4)
Be careful with your wording: after does not allow things to run in parallel. Tkinter is single-threaded and can only do one thing at a time. If something scheduled by after is running, it's not running in parallel with the rest of the code. If both do_something1 and do_something2 are scheduled to run at the same time, they will run sequentially, not in parallel.Deoxygenate
@Neutretto all your solution does is to use the tkinter mainloop instead of sched mainloop, so it works exactly in the same way but allows tkinter interfaces to continue responding. If you're not using tkinter for other things then it doesn't change anything with regard to the sched solution. You can use two or more scheduled functions with different intervals in the sched solution and it will work exactly the same as yours.Cymatium
No, it doesn't work the same way. I explained this. The one "locks" the program (i.e. stops the flow, you can't do anything else -- not even starting another scecduled work as you suggest) until it finishes and the other one lets your hands/free free (i.e. you can do other things after it has started. You don't have to wait unti it finishes. This is a huge difference. If you had tried the method I presented, you would have seen for yourself. I have tried yours. Why don't you try mine too?Neutretto
The important part is if you try to do other stuff before or after calling the respective main loops which are both bocking. So tk.mainloop() and my_scheduler.run().Laurelaureano
V
2

Here's an adapted version to the code from MestreLion. In addition to the original function, this code:

1) add first_interval used to fire the timer at a specific time(caller need to calculate the first_interval and pass in)

2) solve a race-condition in original code. In the original code, if control thread failed to cancel the running timer("Stop the timer, and cancel the execution of the timer’s action. This will only work if the timer is still in its waiting stage." quoted from https://docs.python.org/2/library/threading.html), the timer will run endlessly.

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
Vicarage answered 10/9, 2018 at 9:55 Comment(0)
S
2

Here is another solution without using any extra libaries.

def delay_until(condition_fn, interval_in_sec, timeout_in_sec):
    """Delay using a boolean callable function.

    `condition_fn` is invoked every `interval_in_sec` until `timeout_in_sec`.
    It can break early if condition is met.

    Args:
        condition_fn     - a callable boolean function
        interval_in_sec  - wait time between calling `condition_fn`
        timeout_in_sec   - maximum time to run

    Returns: None
    """
    start = last_call = time.time()
    while time.time() - start < timeout_in_sec:
        if (time.time() - last_call) > interval_in_sec:
            if condition_fn() is True:
                break
            last_call = time.time()
Sweeper answered 20/5, 2020 at 17:49 Comment(0)
H
1

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:

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

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.

Hannie answered 21/2, 2017 at 13:43 Comment(0)
G
1

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())
    return True

# call every minute
glib.timeout_add(60*1000, display_local_time)
Goingover answered 29/9, 2017 at 18:13 Comment(0)
H
1

timed-count can do that to high precision (i.e. < 1 ms) as it's synchronized to the system clock. It won't drift over time and isn't affected by the length of the code execution time (provided that's less than the interval period of course).

A simple, blocking example:

from timed_count import timed_count

for count in timed_count(60):
    # Execute code here exactly every 60 seconds
    ...

You could easily make it non-blocking by running it in a thread:

from threading import Thread
from timed_count import timed_count

def periodic():
    for count in timed_count(60):
        # Execute code here exactly every 60 seconds
        ...

thread = Thread(target=periodic)
thread.start()
Hydroscope answered 26/11, 2022 at 19:32 Comment(0)
K
1

I stumbled upon this trying to write scripts for FL Studio controllers. I don't even program in python.(C++ Programmer). But the top answers are unclear so I will add this.

Using the game developer programming pattern for a timeframe. Usually used to lock parts of a game's update loop to happen at a certain time interval. For example so that a physics engine update is independent from the rendering.

The basic idea is:

  1. Calculate the difference between the current time and last of this "frame"(or call to your function).
  2. Then accumulate the difference in time between frames.
  3. If the accumulated time difference exceeds your target timeframe then we execute the code.
  4. You can also optionally respond and know if your function is delayed.
CurrentTime = time.monotonic()
AccumDiff = 0

def Update():
    global CurrentTime
    global AccumDiff

    LastTime = CurrentTime            # Store previous frame time
    CurrentTime = time.monotonic()    # Get the current frame time
    TimeDiff = CurrentTime - LastTime # Calculate the difference
    AccumDiff += TimeDiff             # Accumulate the difference in time

    # Do something every second
    if AccumDiff >= 1.0: # A second passed!
        Overtime = AccumDiff - 1.0 # (optional) Respond to overtime
        print("This print is delayed by:", Overtime ,"Seconds!")  
        
        # Reset our accumulated time
        AccumDiff = 0
        # Do something every X seconds
        print("Printing............")
    else: # A second has not passed yet.
        # Continue accumulating time (pass or do something in response)
        print("Not gonna print yet.")
    
Kellie answered 24/11, 2023 at 6:52 Comment(0)
A
0
    ''' 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 __name__ == "__main__":
    timeInterval= int(input('Enter Time in Seconds:'))
    printit()
Alage answered 28/8, 2018 at 10:59 Comment(1)
On the basis of user input it will iterate that method at every interval of time.Alage
W
0

I think it depends what you want to do and your question didn't specify lots of details.

For me I want to do an expensive operation in one of my already multithreaded processes. So I have that leader process check the time and only her do the expensive op (checkpointing a deep learning model). To do this I increase the counter to make sure 5 then 10 then 15 seconds have passed to save every 5 seconds (or use modular arithmetic with math.floor):

def print_every_5_seconds_have_passed_exit_eventually():
    """
    https://mcmap.net/q/75315/-run-certain-code-every-n-seconds-duplicate
    https://mcmap.net/q/74048/-how-to-repeatedly-execute-a-function-every-x-seconds
    :return:
    """
    opts = argparse.Namespace(start=time.time())
    next_time_to_print = 0
    while True:
        current_time_passed = time.time() - opts.start
        if current_time_passed >= next_time_to_print:
            next_time_to_print += 5
            print(f'worked and {current_time_passed=}')
            print(f'{current_time_passed % 5=}')
            print(f'{math.floor(current_time_passed % 5) == 0}')
starting __main__ at __init__
worked and current_time_passed=0.0001709461212158203
current_time_passed % 5=0.0001709461212158203
True
worked and current_time_passed=5.0
current_time_passed % 5=0.0
True
worked and current_time_passed=10.0
current_time_passed % 5=0.0
True
worked and current_time_passed=15.0
current_time_passed % 5=0.0
True

To me the check of the if statement is what I need. Having threads, schedulers in my already complicated multiprocessing multi-gpu code is not a complexity I want to add if I can avoid it and it seems I can. Checking the worker id is easy to make sure only 1 process is doing this.

Note I used the True print statements to really make sure the modular arithemtic trick worked since checking for exact time is obviously not going to work! But to my pleasant surprised the floor did the trick.

Wattage answered 19/5, 2021 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.