12

I was trying to figure out how to make a setInterval that cancels in python without making an entire new class to do that, I figured out how but now I'm wondering if there is a better way to do it.

The code below seems to work fine, but I have not thoroughly tested it.

import threading
def setInterval(func, sec):
    def inner():
        while function.isAlive():
            func()
            time.sleep(sec)
    function = type("setInterval", (), {}) # not really a function I guess
    function.isAlive = lambda: function.vars["isAlive"]
    function.vars = {"isAlive": True}
    function.cancel = lambda: function.vars.update({"isAlive": False})
    thread = threading.Timer(sec, inner)
    thread.setDaemon(True)
    thread.start()
    return function
interval = setInterval(lambda: print("Hello, World"), 60) # will print Hello, World every 60 seconds
# 3 minutes later
interval.cancel() # it will stop printing Hello, World 

Is there a way to do the above without making a dedicated class that inherits from threading.Thread or using the type("setInterval", (), {}) ? Or am I stuck in deciding between making a dedicated class or continue to use type

martineau
  • 119,623
  • 25
  • 170
  • 301
user3234209
  • 935
  • 2
  • 8
  • 14

1 Answers1

30

To call a function repeatedly with interval seconds between the calls and the ability to cancel future calls:

from threading import Event, Thread

def call_repeatedly(interval, func, *args):
    stopped = Event()
    def loop():
        while not stopped.wait(interval): # the first call is in `interval` secs
            func(*args)
    Thread(target=loop).start()    
    return stopped.set

Example:

cancel_future_calls = call_repeatedly(60, print, "Hello, World")
# ...
cancel_future_calls() 

Note: this version waits around interval seconds after each call no matter how long func(*args) takes. If metronome-like ticks are desired then the execution could be locked with a timer(): stopped.wait(interval) could be replaced with stopped.wait(interval - timer() % interval) where timer() defines the current time (it may be relative) in seconds e.g., time.time(). See What is the best way to repeatedly execute a function every x seconds in Python?

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • Thank you, this is definitely an improvement, looks cleaner too. – user3234209 Mar 19 '14 at 07:13
  • Be careful about running this in the background, when your main thread throws an unhandled exception - wrap `# ...` in `try: (...) finally: cancel_future_calls()`. (This includes the common `KeyboardInterrupt`). – Tomasz Gandor Oct 28 '16 at 12:51
  • 1
    @TomaszGandor: you could pass `daemon=True` so that the background thread dies if the program exits unexpectedly. Or make it into a context manager: `with calling_repeatedly(interval, func, *args): ` if you want to repeat the function while the code block is executed e.g., to collect some performance statistics: memory, cpu percentage, etc. There could be other cases. Returning the handle which may cancel future calls is the most flexible approach. – jfs Oct 28 '16 at 13:01
  • How do I stop the interval from inside the function – Coder Gautam YT Oct 03 '21 at 14:41
  • @CoderGautamYT: just like with any other Python function, you could use `return` to exit the function. – jfs Oct 03 '21 at 14:48