Postponing functions in python

To execute a function after a delay or to repeat a function in given number of seconds using an event-loop (no threads), you could:

Tkinter

#!/usr/bin/env python
from Tkinter import Tk

def foo():
    print("timer went off!")

def countdown(n, bps, root):
    if n == 0:
        root.destroy() # exit mainloop
    else:
        print(n)
        root.after(1000 / bps, countdown, n - 1, bps, root)  # repeat the call

root = Tk()
root.withdraw() # don't show the GUI window
root.after(4000, foo) # call foo() in 4 seconds
root.after(0, countdown, 10, 2, root)  # show that we are alive
root.mainloop()
print("done")

Output

10
9
8
7
6
5
4
3
timer went off!
2
1
done

Gtk

#!/usr/bin/env python
from gi.repository import GObject, Gtk

def foo():
    print("timer went off!")

def countdown(n): # note: a closure could have been used here instead
    if n[0] == 0:
        Gtk.main_quit() # exit mainloop
    else:
        print(n[0])
        n[0] -= 1
        return True # repeat the call

GObject.timeout_add(4000, foo) # call foo() in 4 seconds
GObject.timeout_add(500, countdown, [10])
Gtk.main()
print("done")

Output

10
9
8
7
6
5
4
timer went off!
3
2
1
done

Twisted

#!/usr/bin/env python
from twisted.internet import reactor
from twisted.internet.task import LoopingCall

def foo():
    print("timer went off!")

def countdown(n):
    if n[0] == 0:
        reactor.stop() # exit mainloop
    else:
        print(n[0])
        n[0] -= 1

reactor.callLater(4, foo) # call foo() in 4 seconds
LoopingCall(countdown, [10]).start(.5)  # repeat the call in .5 seconds
reactor.run()
print("done")

Output

10
9
8
7
6
5
4
3
timer went off!
2
1
done

Asyncio

Python 3.4 introduces new provisional API for asynchronous IO -- asyncio module:

#!/usr/bin/env python3.4
import asyncio

def foo():
    print("timer went off!")

def countdown(n):
    if n[0] == 0:
        loop.stop() # end loop.run_forever()
    else:
        print(n[0])
        n[0] -= 1

def frange(start=0, stop=None, step=1):
    while stop is None or start < stop:
        yield start
        start += step #NOTE: loss of precision over time

def call_every(loop, seconds, func, *args, now=True):
    def repeat(now=True, times=frange(loop.time() + seconds, None, seconds)):
        if now:
            func(*args)
        loop.call_at(next(times), repeat)
    repeat(now=now)

loop = asyncio.get_event_loop()
loop.call_later(4, foo) # call foo() in 4 seconds
call_every(loop, 0.5, countdown, [10]) # repeat the call every .5 seconds
loop.run_forever()
loop.close()
print("done")

Output

10
9
8
7
6
5
4
3
timer went off!
2
1
done

Note: there is a slight difference in the interface and behavior between these approaches.


You want a Timer object from the threading module.

from threading import Timer
from time import sleep

def foo():
    print "timer went off!"
t = Timer(4, foo)
t.start()
for i in range(11):
    print i
    sleep(.5)

If you want to repeat, here's a simple solution: instead of using Timer, just use Thread but pass it a function that works somewhat like this:

def call_delay(delay, repetitions, func, *args, **kwargs):             
    for i in range(repetitions):    
        sleep(delay)
        func(*args, *kwargs)

This won't do infinite loops because that could result in a thread that won't die and other unpleasant behavior if not done right. A more sophisticated approach might use an Event-based approach, like this one.


Asynchronous callbacks like Javascript's setTimeout require an event-driven architecture.

Asynchronous frameworks for Python like the popular twisted have CallLater which does what you want, but it means adopting the event-driven architecture in your application.

Another alternative is to use threads and to sleep in a thread. Python providers a timer to make the waiting part easy. However, when your thread awakes and your function executes, it is in a separate thread and must do whatever it does in a thread-safe manner.


Sorry, I can't post more than 2 links, so for more information please check PEP 380 and most importantly the documentation of asyncio.

asyncio is the preferred solution to this kind of question unless you insist on threading or multiprocessing. It is designed and implemented by GvR under the name "Tulip". It has been introduced by GvR on PyCon 2013 with the intention to be the one event-loop to rule (and standardize) all event-loops (like the ones in twisted, gevent, etc.) and make them compatible with each other. asyncio has been mentioned before, but the true power of asyncio is unleashed with yield from.

# asyncio is in standard lib for latest python releases (since 3.3)
import asyncio

# there's only one event loop, let's fetch that
loop = asyncio.get_event_loop()

# this is a simple reminder that we're dealing with a coro
@asyncio.coroutine
def f():
    for x in range(10):
        print(x)
        # we return with a coroutine-object from the function, 
        # saving the state of the execution to return to this point later
        # in this case it's a special sleep
        yield from asyncio.sleep(3)

# one of a few ways to insert one-off function calls into the event loop
loop.call_later(10, print, "ding!")
# we insert the above function to run until the coro-object from f is exhausted and 
# raises a StopIteration (which happens when the function would return normally)
# this also stops the loop and cleans up - keep in mind, it's not DEAD but can be restarted
loop.run_until_complete(f())
# this closes the loop - now it's DEAD
loop.close()

================

>>> 
0
1
2
3
ding!
4
5
6
7
8
9
>>>

JavaScript can do this because it runs things in an event loop. This can be done in Python through use of an event loop such as Twisted, or via a toolkit such as GLib or Qt.