21

I have recently posted a question about how to postpone execution of a function in Python (kind of equivalent to Javascript setTimeout) and it turns out to be a simple task using threading.Timer (well, simple as long as the function does not share state with other code, but that would create problems in any event-driven environment).

Now I am trying to do better and emulate setInterval. For those who are not familiar with Javascript, setInterval allows to repeat a call to a function every x seconds, without blocking the execution of other code. I have created this example decorator:

import time, threading

def setInterval(interval, times = -1):
    # This will be the actual decorator,
    # with fixed interval and times parameter
    def outer_wrap(function):
        # This will be the function to be
        # called
        def wrap(*args, **kwargs):
            # This is another function to be executed
            # in a different thread to simulate setInterval
            def inner_wrap():
                i = 0
                while i != times:
                    time.sleep(interval)
                    function(*args, **kwargs)
                    i += 1
            threading.Timer(0, inner_wrap).start()
        return wrap
    return outer_wrap

to be used as follows

@setInterval(1, 3)
def foo(a):
    print(a)

foo('bar')
# Will print 'bar' 3 times with 1 second delays

and it seems to me it is working fine. My problem is that

  • it seems overly complicated, and I fear I may have missed a simpler/better mechanism
  • the decorator can be called without the second parameter, in which case it will go on forever. When I say foreover, I mean forever - even calling sys.exit() from the main thread will not stop it, nor will hitting Ctrl+c. The only way to stop it is to kill python process from the outside. I would like to be able to send a signal from the main thread that would stop the callback. But I am a beginner with threads - how can I communicate between them?

EDIT In case anyone wonders, this is the final version of the decorator, thanks to the help of jd

import threading

def setInterval(interval, times = -1):
    # This will be the actual decorator,
    # with fixed interval and times parameter
    def outer_wrap(function):
        # This will be the function to be
        # called
        def wrap(*args, **kwargs):
            stop = threading.Event()

            # This is another function to be executed
            # in a different thread to simulate setInterval
            def inner_wrap():
                i = 0
                while i != times and not stop.isSet():
                    stop.wait(interval)
                    function(*args, **kwargs)
                    i += 1

            t = threading.Timer(0, inner_wrap)
            t.daemon = True
            t.start()
            return stop
        return wrap
    return outer_wrap

It can be used with a fixed amount of repetitions as above

@setInterval(1, 3)
def foo(a):
    print(a)

foo('bar')
# Will print 'bar' 3 times with 1 second delays

or can be left to run until it receives a stop signal

import time

@setInterval(1)
def foo(a):
    print(a)

stopper = foo('bar')

time.sleep(5)
stopper.set()
# It will stop here, after printing 'bar' 5 times.
Community
  • 1
  • 1
Andrea
  • 20,253
  • 23
  • 114
  • 183
  • I was looking over your code and I was a bit confused because I didn't see what causes the loop to actually stop. It seemed to me that this would actually result in a loop that calls `function` slowly until `stopper.set()` is called, at which point the loop would suddenly accelerate! I checked, and indeed, if you add another `time.sleep(5)` after `stopper.set()` in the example above, that's exactly what happens. You need to incorporate some logic to make sure that the loop stops once `stop.is_set()` is true -- e.g. `while i != times and not stop.is_set():` – senderle Mar 04 '11 at 04:23
  • You are right, I misinterpreted one paragraph in the doc about the `wait()` method, and I did not catch the problem with my tests. I will now correct the code. – Andrea Mar 04 '11 at 10:03
  • I've posted the answer to your [other question](http://stackoverflow.com/questions/5177439/postponing-functions-in-python) that demonstrates [how to emulate Javascript's `setInterval()` without threads in Tkinter, Gtk, Twisted](http://stackoverflow.com/a/14040516/4279) – jfs Dec 26 '12 at 12:32
  • Here's a [simple Event-based `call_repeatedly(interval, func, *args)` implementation](http://stackoverflow.com/a/22498708/4279) – jfs Oct 22 '16 at 13:40

4 Answers4

11

Your solution looks fine to me.

There are several ways to communicate with threads. To order a thread to stop, you can use threading.Event(), which has a wait() method that you can use instead of time.sleep().

stop_event = threading.Event()
...
stop_event.wait(1.)
if stop_event.isSet():
    return
...

For your thread to exit when the program is terminated, set its daemon attribute to True before calling start(). This applies to Timer() objects as well because they subclass threading.Thread. See http://docs.python.org/library/threading.html#threading.Thread.daemon

jd.
  • 10,678
  • 3
  • 46
  • 55
  • Wonderful! I was able to remove the dependency on the time module and to manage to get communication. Now the callback can easily be stopped. :-) – Andrea Mar 03 '11 at 11:14
3

Maybe these are the easiest setInterval equivalent in python:

 import threading

 def set_interval(func, sec):
    def func_wrapper():
        set_interval(func, sec) 
        func()  
    t = threading.Timer(sec, func_wrapper)
    t.start()
    return t
stamat
  • 1,832
  • 21
  • 26
  • The reference to the inner timers gets lost. If the first Timer has already fired, there is no reference to stop the subsequent ones. – htellez Mar 02 '23 at 18:20
2

Maybe a bit simpler is to use recursive calls to Timer:

from threading import Timer
import atexit

class Repeat(object):

    count = 0
    @staticmethod
    def repeat(rep, delay, func):
        "repeat func rep times with a delay given in seconds"

        if Repeat.count < rep:
            # call func, you might want to add args here
            func()
            Repeat.count += 1
            # setup a timer which calls repeat recursively
            # again, if you need args for func, you have to add them here
            timer = Timer(delay, Repeat.repeat, (rep, delay, func))
            # register timer.cancel to stop the timer when you exit the interpreter
            atexit.register(timer.cancel)
            timer.start()

def foo():
    print "bar"

Repeat.repeat(3,2,foo)

atexit allows to signal stopping with CTRL-C.

Bernhard
  • 8,583
  • 4
  • 41
  • 42
  • Thank you for your suggestion, but I already accepted jd's answer. In any case I prefer sleeping instead of recursively creating new Timers - by the way, would this spawn infintely many threads, or are the threads killed when the function returns, even though a thread they created is still open? – Andrea Mar 03 '11 at 11:17
  • AFAIK it will spawn rep-many threads, they all terminate when the last Timer has finished. – Bernhard Mar 03 '11 at 11:38
  • 1
    This may be annoying if you want to repeat the function thousands of times, or even forever. Thank you for your input anyway! :-) – Andrea Mar 03 '11 at 14:17
0

this class Interval

class ali:
    def __init__(self):
        self.sure = True;

    def aliv(self,func,san):
        print "ali naber";
        self.setInterVal(func, san);

    def setInterVal(self,func, san):

        # istenilen saniye veya dakika aralığında program calışır. 
        def func_Calistir():

            func(func,san);  #calışıcak fonksiyon.
        self.t = threading.Timer(san, func_Calistir)
        self.t.start()
        return self.t


a = ali();
a.setInterVal(a.aliv,5);
ali
  • 1