Welcome, guest | Sign In | My Account | Store | Cart

This simple generator function is used to call a function X times per second.

Python, 13 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def timed_call(callback, calls_per_second, *args, **kw):
    """
    Create an iterator which will call a function a set number
    of times per second.
    """
    time_time = time.time
    start = time_time()
    period = 1.0 / calls_per_second
    while True:
        if (time_time() - start) > period:
            start += period
            callback(*args, **kw)
        yield None

This iterator is useful for many things. I use it to control timed updates to objects in my games. It is especially useful for animating objects at a constant frame rate across systems with different processor speeds and video refresh rates.

You can call the iterator as often as possible in the main loop, and be assured that it will only run the callback function X times per second.

3 comments

jackdied 16 years, 8 months ago  # | flag

timing error. This recipe will call the function at most the number of times per second. You don't want to reset the start time to NOW after the call but to increment it by the period. If the time between calls should be 5 seconds and the function call takes 1 second you want to wait 4 seconds before calling it again, not 5.

S W (author) 16 years, 8 months ago  # | flag

Ah yes, agreed. The callback functions I'm using are so small, I did not detect any timing errors. Updating recipe...

Raymond Hettinger 16 years, 8 months ago  # | flag

Alternate Strategy. In the context of game updates, the recipe's strategy bogs down when there are many recurring events. The caller is burdened by having to make frequent, unnecessary calls to event.next() in order to check whether each recurring event is ready to run. Not knowing which event is to occur next, the caller has to try calling each until one fires off -- essentially, this is a linear search. All of these calls consume CPU time even when events are scheduled for infrequent update intervals.

Instead of a linear search, the order of execution can be kept in a priority queue so that only the next scheduled event is called. Another improvement is use time.sleep() between events so as to not eat-up CPU time that could be used by other threads. The API can be improved by creating a task manager responsible for making the calls (instead of burdening the caller with the responsibility for tracking each event separately). Since these three improvements are already encapsulated in the sched module, an implementation is straight-forward:

import sched, time

scheduler = sched.scheduler(time.time, time.sleep)

def new_timed_call(calls_per_second, callback, *args, **kw):
    period = 1.0 / calls_per_second
    def reload():
        callback(*args, **kw)
        scheduler.enter(period, 0, reload, ())
    scheduler.enter(period, 0, reload, ())

#### example code ####

def p(c):
    "print the specified character"
    print c,
new_timed_call(3, p, '3')  # print '3' three times per second
new_timed_call(6, p, '6')  # print '6' six times per second
new_timed_call(9, p, '9')  # print '9' nine times per second
scheduler.run()

Note, the order of arguments was changed from the original. For better readability, the callback function needs to be listed adjacent to its arguments.

Also note, Windows users should substitute time.clock for time.time.

Created by S W on Wed, 4 May 2005 (PSF)
Python recipes (4591)
S W's recipes (20)

Required Modules

  • (none specified)

Other Information and Tasks