27

The best solution I've found so far is to just use the sleep() function. I'd like to run my own callback function when the event of a timer expiration happens. Is there any event-driven way to go about it?

from time import sleep

# Sleep for a minute
time.sleep(60)
2
  • There are several event/queue timer libraries. I use Twisted's reactor but that's because I need Twisted for other things. You definitely don't want to sleep your thread if you application has anything else to do.
    – Geoff Genz
    Commented Mar 4, 2014 at 19:18
  • 1
    Is that question answered for you? If so, would you accept one of the posted answers? If not, do you have an answer of your own to post?
    – Bach
    Commented Jul 16, 2014 at 12:51

4 Answers 4

36

There's a built-in simple solution, using the threading module:

import threading

timer = threading.Timer(60.0, callback)
timer.start()  # after 60 seconds, 'callback' will be called

## (in the meanwhile you can do other stuff...)

You can also pass args and kwargs to your callback. See here.

2
  • 2
    i can't seem to reset the timer, though. I don't want this to run continuosly. i want to start it on another event and then get an expiration event. when i try to timer.cancel(), i get:raise RuntimeError("threads can only be started once") RuntimeError: threads can only be started once
    – tarabyte
    Commented Mar 4, 2014 at 20:23
  • 6
    You can't / don't need to reset the timer. It works only once.
    – Bach
    Commented Mar 4, 2014 at 20:25
13

I think it could be really simple. Take a look at this example. It works even in a python console!

from threading import Thread
from time import sleep

# Function to be called when the timer expires
def myFunction():
    print 'Did anyone call me?'

# Function with the timer
def myTimer(seconds):
    sleep(seconds)
    myFunction()

# Thread that will sleep in background and call your function
# when the timer expires.
myThread = Thread(target=myTimer, args=(4,))
myThread.start()

Put whatever amount of seconds you want, and keep working with the console or running the main thread/programm. You will notice that the function will be called when the timer comes to an end.

Edit

Another good example, considering the comment from @tarabyte is the one where the function is called only depending on the value of some variable or flag. I hope this would then be the answer @tarabyte is looking for.

from threading import Thread
from time import sleep

myFlag = False

# Function to be called when the flag turns on
def myFunction():
    print 'Did anyone call me?'

def myTimer():
    global myFlag
    while True:
        if myFlag:
            myFunction()
            myFlag = False
        else:
            sleep(1)

# Thread that will sleep in background and call your function
# when the myFlag turns to be True
myThread = Thread(target=myTimer)
myThread.start()

# Then, you can do whatever you want and later change the value of myFlag.
# Take a look at the output inside ipython when the value of myFlag is changed.


In [35]: myFlag
Out[35]: False

In [36]: myFlag = True

In [37]: Did anyone call me?
3
  • Great example, but I should have clarified that I'm not interested in recurring expirations, just ones that I would like to .start() myself and and then end after the first go. I guess I can hack a solution by wrapping the contents of myTimer in an if (some_var_i_set_elsewhere)
    – tarabyte
    Commented Mar 4, 2014 at 20:31
  • 1
    Hi @tarabyte. Take a look please at the edited version of my post. I think this is more or less what you want to do. You can modify the type and possible values of myFlag in order to adjust it to your needs. If you agree with me, please, accept the answer as correct and mark it.
    – Javier
    Commented Mar 5, 2014 at 10:04
  • @Javier "It works even in a python console" - all python code works the same in the console as in a script. Only some code works only in the console, and not in a python script. For example, somevar in the console prints the value of somevar if it was defined before, but the same in a python script does nothing.
    – TheEagle
    Commented Feb 15, 2021 at 16:06
9

Sometimes a simple solution is best, even if it polls the time. I have used this to great success before - it doesn't block if your thread doesn't stop on it.

I think I would manage this most simply by checking times, since this is so much more simple and resource economical than working out a separate threaded solution:

def event_minute_later(event):
    print(time.time()) # use for testing, comment out or delete for production
    return event + 60 < time.time()

And usage:

>>> event = time.time()
>>> print(event)
1393962502.62

>>> event_minute_later(event)
1393962526.73
False
>>> event_minute_later(event)
1393962562.9
True
0
3

Since Python 3.7 (and older versions have reached end of life by now) the asyncio built-in module lets you add a Python sleep() call asynchronously:

import asyncio

async def test():
    print("Hello ... but wait, there is more!")
    await asyncio.sleep(3)
    print("... in the async world!")

Here's some proof that it is non-blocking (courtesy of RealPython):

import asyncio
# Jupyter Notebook users need to allow
# nesting of the asyncio event loop
import nest_asyncio
nest_asyncio.apply()
import time

async def workload(text, duration):
    while duration > 0:
        # run sleep and yield control 
        # back to the event loop (for one cycle)
        await asyncio.sleep(1)
        print(f'{text} counter: sleeping {duration} seconds')
        duration -= 1

async def main():
    # send the workload() coroutine to the background,
    # to let it run concurrently with other tasks, 
    # switching between them at await points
    task_1 = asyncio.create_task(workload('First', 2))
    task_2 = asyncio.create_task(workload('Second', 4))
    task_3 = asyncio.create_task(workload('Third', 8))
    print(f"Started: {time.strftime('%X')}")
    # create await points for each 
    # of the concurrent tasks
    await task_1
    await task_2
    await task_3
    print(f"Ended: {time.strftime('%X')}")

if __name__ == '__main__':
    asyncio.run(main())

Output:

Started: 09:07:21
First counter: sleeping 2 seconds
Second counter: sleeping 4 seconds
Third counter: sleeping 8 seconds
First counter: sleeping 1 seconds
Second counter: sleeping 3 seconds
Third counter: sleeping 7 seconds
Second counter: sleeping 2 seconds
Third counter: sleeping 6 seconds
Second counter: sleeping 1 seconds
Third counter: sleeping 5 seconds
Third counter: sleeping 4 seconds
Third counter: sleeping 3 seconds
Third counter: sleeping 2 seconds
Third counter: sleeping 1 seconds
Ended: 09:07:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.