Search code examples
pythonpython-3.xclocktiming

Python time variable is not changing


I modified some code I found here on stackoverflow, but I get some weird behaviour. Can anyone see my error?

import atexit
from time import clock

line = "="*10 + ">>>"

def secondsToStr(s):
    return "{0}.{1}ms".format(round(s*1000), round(s*1000000))

##
# @brief Starts a timer. If the program crashes, the end function will be called anyway.
def start():
    atexit.register(stop)
    print(line,"Start time measurement")
    startTime = clock()
    print("new start time:", startTime)

##
# @brief Needs to be called after start(). Prints the time passed since start() was called.
def stop():
    end = clock()
    elapsed = end-startTime
    print(startTime, end)    # Inserted for debugging
    print(line, "Ellapsed time:", secondsToStr(elapsed))
    atexit.unregister(stop)

def now():
    return secondsToStr(clock())

startTime = clock()

The idea is to call start() and stop() to measure the time the program takes in between those two calles. Weirdly, the startTime does not change. Thus every call of stop() gives the past time since the first call of start(). Here is an example output:

==========>>> Start time measurement
new start time: 0.285078
Doing work
0.231932 1.766478
==========>>> Ellapsed time: 1535.1534546ms

==========>>> Start time measurement
new start time: 1.766624
More work
0.231932 1.975752
==========>>> Ellapsed time: 1744.1743820ms

==========>>> Start time measurement
new start time: 1.975821
More work
0.231932 1.976301
==========>>> Ellapsed time: 1744.1744369ms

Why is the startTime never changing? It should get a new value every time start() is called.


Solution

  • The reason it doesn't work is because inside start, startTime is a local variable. If you want to update the global variable, you need to tell python that explicitly:

    def start():
        global startTime  # tell python to work with `startTime` in the global scope.
        atexit.register(stop)
        print(line,"Start time measurement")
        startTime = clock()
        print("new start time:", startTime)
    

    Note however that 99% of the time, when you use the global statement, there's probably a better alternative. Without knowing your exact use-case, it's hard to give a perfect suggestion. However, I might consider a context manager here:

    import contextlib
    from time import clock
    
    line = "="*10 + ">>>"
    
    def secondsToStr(s):
        return "{0}.{1}ms".format(round(s*1000), round(s*1000000))
    
    @contextlib.contextmanager
    def timer():
        start = clock()
        try:
            yield None
        finally:
            stop = clock()
            print(line, "Ellapsed time:", secondsToStr(elapsed))
    

    You'd use this as follows:

    with timer():
        do_something()
        do_something_else()
    

    And after the functions return, you'll get the combined runtime of the two functions printed. As with your original, this will still print if an exception occurs.