481

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?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
davidmytton
  • 38,604
  • 37
  • 87
  • 93
  • 166
    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. – Simon Jan 23 '09 at 21:12
  • Dupe: http://stackoverflow.com/questions/373335/suggestions-for-a-cron-like-scheduler-in-python – James Brady Jan 23 '09 at 22:14
  • 7
    also `time.sleep(60)` may return both earlier and later – jfs Mar 19 '14 at 07:25
  • 9
    I am still wondering: *Are there any foreseeable problems with this code?* – dwitvliet Jan 27 '15 at 18:39
  • 9
    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. – litepresence Feb 21 '17 at 14:28
  • See [Python Scheduling](https://martin-thoma.com/python-scheduling/) for a tiny simple example. – Martin Thoma Aug 22 '17 at 08:11
  • 13
    @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 x – DGoiko Oct 31 '18 at 11:46
  • 2
    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?) – smci Sep 12 '19 at 00:04
  • 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? – Askar Oct 27 '22 at 18:12

22 Answers22

387

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.

nosklo
  • 217,122
  • 57
  • 293
  • 297
  • 30
    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()? – Baishampayan Ghose Jan 23 '09 at 21:13
  • 2
    @Baishampayan: Just schedule a new run. – nosklo Jan 23 '09 at 21:18
  • Kronos, based on sched, offers a higher level interface: http://www.razorvine.net/download/kronos.py Used by TurboGears. – James Brady Jan 23 '09 at 22:15
  • 5
    Then apscheduler at http://packages.python.org/APScheduler/ should also get a mention at this point. – Daniel F Jan 27 '13 at 20:06
  • The documentation of the shed module also points to the threading.Timer class, which is better suited for multithreaded environments. http://docs.python.org/2/library/threading.html#threading.Timer An example can be found in the sched module documentation. – Daniel F Jan 28 '13 at 21:40
  • 14
    note: this version may drift. You could use `enterabs()` to avoid it. Here's [a non-drifting version for comparison](http://stackoverflow.com/a/25251804/4279). – jfs Oct 28 '14 at 16:45
  • @J.F.Sebastian: Why is this version can drift? – JavaSa Jan 25 '17 at 14:55
  • 12
    @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 comment](http://stackoverflow.com/questions/474528/what-is-the-best-way-to-repeatedly-execute-a-function-every-x-seconds-in-python/474543?noredirect=1#comment64793337_25251804) – jfs Jan 25 '17 at 15:42
  • 1
    This does not answer the question. – kleinfreund Jul 29 '17 at 14:29
  • OK, but how does exit from this timer (loop)? – Apostolos Mar 03 '18 at 17:02
  • @Apostolos 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 loop – nosklo Mar 07 '18 at 20:04
  • 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. – Apostolos Mar 09 '18 at 08:15
  • @Apostolos 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. – nosklo Mar 13 '18 at 03:40
  • 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.) – Apostolos Mar 14 '18 at 08:36
  • @Apostolos 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. – nosklo Jun 05 '18 at 19:56
  • 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. – Apostolos Jun 07 '18 at 09:01
  • @Apostolos 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 want – nosklo Jun 07 '18 at 18:55
  • 1
    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. – DGoiko Oct 31 '18 at 11:48
  • 1
    @DGoiko 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. – nosklo Oct 31 '18 at 11:54
  • @nosklo 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? – DGoiko Oct 31 '18 at 13:01
  • It seems youŕe right @DGoiko the clock starts just after `enter` if you schedule a new run – nosklo Oct 31 '18 at 13:06
  • 3
    You could move `s.enter(...)` to the start of the function to reduce drift. Also, what is the point of `sc`? – Solomon Ucko Dec 18 '19 at 01:19
  • 1
    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 sc –  Mar 25 '20 at 15:29
  • adding this code to my django project made it hang on `Performing system checks...` step – LucasSeveryn Sep 17 '20 at 12:18
  • How to use `asyncio` in this case? – Guilherme Matheus Jan 03 '22 at 21:36
  • 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 time – Junfeng Mar 29 '23 at 02:43
356

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...

Massimo
  • 3,171
  • 3
  • 28
  • 41
Dave Rove
  • 3,743
  • 1
  • 14
  • 2
  • 55
    +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. – jfs Sep 18 '14 at 07:09
  • 19
    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... – Angry 84 Jan 01 '16 at 22:48
  • 12
    I prefer `from time import time, sleep` because of the existential implications ;) – Will Feb 16 '16 at 04:40
  • 1
    @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. – jfs Feb 22 '16 at 21:44
  • 21
    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. – TemporalWolf Jul 14 '16 at 19:22
  • @J.F.Sebastian: What is the purpose of the `% 60` at the end? – Train Heartnet Aug 01 '16 at 20:20
  • 3
    @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)`](http://stackoverflow.com/a/26609843). Compare it with just `while keep_doing_it(): sleep(interval)` where errors may accumulate after several iterations. – jfs Aug 01 '16 at 20:51
  • 2
    @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. –  Feb 25 '17 at 16:21
  • 1
    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 years –  Feb 25 '17 at 16:32
  • 1
    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. – tgikal Aug 21 '18 at 16:40
  • I think `starttime=time.time()` should also come as first line inside the `while` loop. – backslashN Apr 15 '19 at 07:24
  • @backslashN 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. – smoothware Apr 16 '19 at 21:28
  • @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. – JenyaKh Apr 17 '19 at 10:38
  • @TemporalWolf This will run every 60 seconds. If the function takes 28 seconds to run, time.sleep will be 32. FYI. – Jossie Calderon Apr 27 '20 at 19:13
  • 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). – eraoul Jul 18 '20 at 01:55
  • 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.) – Jakob Drachmann Havtorn Sep 10 '20 at 12:55
  • Thanks for this. So simple and elegant. And like all great solutions, so obvious when you know it. – Vahid S. Bokharaie Jun 20 '21 at 15:39
  • 1
    `time.monotonic()` would be more appropriate here to a avoid daylight time savings and other related issues. – Louis Lac Oct 21 '22 at 16:29
108

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!
MestreLion
  • 12,698
  • 8
  • 66
  • 57
  • 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. – eraoul Dec 05 '16 at 00:21
  • 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. – Rich Episcopo Dec 21 '16 at 20:08
  • 2
    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. – Rich Episcopo Dec 21 '16 at 23:09
  • 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. – MestreLion Feb 06 '17 at 15:12
  • This is the only non-blocking way I could get. Thanks. – backslashN Apr 24 '19 at 08:12
  • 1
    @eraoul : 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 `cron` – MestreLion Apr 24 '19 at 16:06
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

Benyamin Jafari
  • 27,880
  • 26
  • 135
  • 150
Aaron Maenpaa
  • 119,832
  • 11
  • 95
  • 108
50

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!
eraoul
  • 1,072
  • 12
  • 19
49
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.
Alfe
  • 56,346
  • 20
  • 107
  • 159
  • 2
    best answer for not drifting. – Sebastian Stark Sep 16 '18 at 13:59
  • 1
    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 minute – PirateApp Feb 21 '19 at 07:52
  • 1
    @PirateApp 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. – Alfe Feb 21 '19 at 09:33
  • 1
    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 question – PirateApp Feb 21 '19 at 09:34
  • 2
    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. – Alfe Feb 21 '19 at 09:38
  • @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. – user50473 Oct 15 '19 at 10:49
  • 1
    @user50473 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 :) – Alfe Oct 15 '19 at 13:20
  • 1
    Sleep can be replace by threading.Event wait with timeout to be more responsive on application exit. https://stackoverflow.com/questions/29082268/python-time-sleep-vs-event-wait – themadmax Dec 24 '19 at 14:39
  • 1
    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. – Dirk Jan 13 '20 at 15:15
36

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

Adrian P
  • 6,479
  • 4
  • 38
  • 55
Itxaka
  • 767
  • 6
  • 10
  • 55
    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. – kommradHomer Sep 18 '13 at 10:09
  • 1
    @kommradHomer: [Dave Rove's answer](http://stackoverflow.com/a/25251804/4279) demonstrates that you *can* use `time.sleep()` run something every X seconds – jfs Sep 21 '14 at 04:56
  • 2
    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)` – Leonard Lepadatu Nov 17 '17 at 13:09
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)
Union find
  • 7,759
  • 13
  • 60
  • 111
  • 1
    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? –  Jan 30 '20 at 13:24
  • 2
    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. – eatmeimadanish Dec 02 '20 at 16:29
  • 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. – Nicholas Kinar Jul 06 '21 at 16:52
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

Sivaram Rasathurai
  • 5,533
  • 3
  • 22
  • 45
  • 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. – BubbleMaster Nov 17 '21 at 15:39
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
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
Anay
  • 209
  • 2
  • 6
6

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.

Adam Hughes
  • 14,601
  • 12
  • 83
  • 122
  • 2
    No, it prints only the first datetime and then stops... – Alex Poca Jun 16 '20 at 09:52
  • Are you sure - I just copy and pasted in terminal. It returns right away but the printout continues in background for me. – Adam Hughes Jun 16 '20 at 14:02
  • 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... – Alex Poca Jun 17 '20 at 07:07
  • hmm strange - I am on python 3.6.10 but don't know why that would matter – Adam Hughes Jun 17 '20 at 13:35
  • 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? – Alex Poca Jun 18 '20 at 08:36
  • 1
    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. – eraoul Jul 18 '20 at 01:59
  • 2
    @eraoul I know, my answer does mention that. I've boldened that portion so it stands out better. – Adam Hughes Jul 18 '20 at 15:11
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.

bobince
  • 528,062
  • 107
  • 651
  • 834
4

Simply use

import time

while True:
    print("this will run after every 30 sec")
    #Your code here
    time.sleep(30)
Swadeshi
  • 1,596
  • 21
  • 33
2

One possible answer:

import time
t=time.time()

while True:
    if time.time()-t>10:
        #run your task here
        t=time.time()
sks
  • 197
  • 1
  • 3
  • 14
  • 4
    This is busy waiting an therefore very bad. – Alfe Apr 12 '18 at 15:57
  • Good solution for someone looking for a non blocking timer. – Noel Jan 22 '19 at 11:44
  • 2
    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. – Nick Lothian May 18 '21 at 00:42
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).

Apostolos
  • 3,115
  • 25
  • 28
  • 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. – Bryan Oakley Mar 14 '18 at 12:23
  • @Apostolos 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. – nosklo Mar 15 '18 at 19:39
  • 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? – Apostolos Mar 19 '18 at 08:17
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
dproc
  • 63
  • 6
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()
Dat
  • 5,405
  • 2
  • 31
  • 32
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.

litepresence
  • 3,109
  • 1
  • 27
  • 35
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)
rise.riyo
  • 64
  • 5
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()
101
  • 8,514
  • 6
  • 43
  • 69
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()
raviGupta
  • 27
  • 4
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://stackoverflow.com/questions/3393612/run-certain-code-every-n-seconds
    https://stackoverflow.com/questions/474528/what-is-the-best-way-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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Charlie Parker
  • 5,884
  • 57
  • 198
  • 323