3

I want to use timeit in Python 3.5 to measure two functions. The first one relies on import math and the second one on from math import log2. I though I can handle this by passing the appropiate import statement as setup string when calling timeit.repeat. But I get NameError: name 'math' is not defined. I don't want to pass the functions as their name strings. When the functions don't depend on imports, this code here works, but I need them with the calls of math.log2

Looking foward to your answers.

def spam_log_v1():
    for _ in range(1000):
        math.log2(2)


def spam_log_v2():
    for _ in range(1000):
        log2(2)


if __name__ == '__main__':

    import timeit

    repeat = 3
    number = 1000
    unit = "usec"
    unittosec = {"usec": 1e6, "msec": 1000, "sec": 1}
    tests = [(spam_log_v1, 'import math'),
             (spam_log_v2, 'from math import log2')]

    for fct, setup in tests:
        res = timeit.repeat(fct, setup=setup, repeat=repeat, number=number)
        print("%s: %d loops, best of %d: %.3g %s per loop" %
              (fct.__name__, number, repeat,
               min(res) / number * unittosec[unit], unit))
4
  • Huh. Usually we see the opposite problem - __main__ has the imports done, but the code being timed is in a scope created by timeit and can't see the imported things. Here, timeit does the imports, but functions from __main__ can't see them. Commented Jan 26, 2016 at 18:40
  • Where is your import math line? Commented Jan 26, 2016 at 18:49
  • @boardrider: In a string in the tests list, where spam_log_v1 can't see it. Commented Jan 26, 2016 at 18:50
  • Possible duplicate of Getting "global name 'foo' is not defined" with Python's timeit Commented Sep 20, 2017 at 16:21

1 Answer 1

4

The inner function which is run by timeit.repeat looks like this:

def inner(_it, _timer{init}):
    {setup}
    _t0 = _timer()
    for _i in _it:
        {stmt}
    _t1 = _timer()
    return _t1 - _t0

where {setup} is replaced by the setup string and {stmt} is replaced by the statement string. Notice that the setup is run from inside inner. So if you place an import statement in setup, the module name becomes a local variable of inner. Later, when spam_log_v1 is called from the stmt, the module name is not available to spam_log_v1 since the module name is not in the global scope.

The fix is to simply import math and/or from math import log2 at the global level of your script.

import math
from math import log2

def spam_log_v1():
    for _ in range(1000):
        math.log2(2)

def spam_log_v2():
    for _ in range(1000):
        log2(2)

if __name__ == '__main__':
    import timeit
    repeat = 3
    number = 1000
    unit = "usec"
    unittosec = {"usec": 1e6, "msec": 1000, "sec": 1}
    tests = [
        (spam_log_v1, ''),
        (spam_log_v2, '')]

    for fct, setup in tests:
        res = timeit.repeat(fct, setup=setup, repeat=repeat, number=number)
        print("%s: %d loops, best of %d: %.3g %s per loop" %
              (fct.__name__, number, repeat,
               min(res) / number * unittosec[unit], unit))
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.