how to pass parameters of a function when using timeit.Timer()

Question:

This is the outline of a simple program

# some pre-defined constants
A = 1
B = 2

# function that does something critical
def foo(num1, num2):
    # do something

# main program.... do something to A and B
for i in range(20):
    # do something to A and B
    # and update A and B during each iteration

import timeit
t = timeit.Timer(stmt="foo(num1,num2)")  
print t.timeit(5)

I just keep getting “global name foo is not defined”…..
Can anyone help me on this? Thanks!

Asked By: CppLearner

||

Answers:

The code snippets must be self-contained – they cannot make external references. You must define your values in the statement-string or setup-string:

import timeit

setup = """
A = 1
B = 2

def foo(num1, num2):
    pass

def mainprog():
    global A,B
    for i in range(20):
        # do something to A and B
        foo(A, B)
"""

t = timeit.Timer(stmt="mainprog()" setup=setup)
print(t.timeit(5))

Better yet, rewrite your code to not use global values.

Answered By: Hugh Bothwell

Your function needs to be define in the setup string. A good way to do this is by setting up your code in a module, so you simple have to do

t = timeit.Timer("foo(num1, num2)", "from myfile import foo")
t.timeit(5)

Otherwise, you’ll have to define all of the setup as a string inside the setup statement.

setup = """
 # some pre-defined constants
A = 1
B = 2

# function that does something critical
def foo(num1, num2):
    # do something

# main program.... do something to A and B
for i in range(20):
    # do something to A and B
    # and update A and B during each iteration
"""

t = timeit.Timer("foo(num1, num2)", setup)
t.timeit(5)

Something awesome I just found out about is a shortcut for iPython that uses cProfile.

def foo(x, y):
    print x*y

%prun foo("foo", 100)

Supposing that your module filename is test.py

# some pre-defined constants
A = 1
B = 2

# function that does something critical
def foo(n, m):
    pass

# main program.... do something to A and B
for i in range(20):
    pass

import timeit
t = timeit.Timer(stmt="test.foo(test.A, test.B)", setup="import test")  
print t.timeit(5)
Answered By: Lucas S.

I usually create an extra function:

def f(x,y):
    return x*y

v1 = 10
v2 = 20

def f_test():
    f(v1,v2)

print(timeit.timeit("f_test()", setup="from __main__ import f_test"))
Answered By: Vinicius Veroneze

The functions can use arguments in timeit if these are created using closures, we can add this behaviours by wrapping them in another function.

def foo(num1, num2):
    def _foo():
        # do something to num1 and num2
        pass
    return _foo

A = 1
B = 2

import timeit
t = timeit.Timer(foo(A,B))  
print(t.timeit(5))

or shorter, we can use functools.partial instead of explicit closures declaration

def foo(num1, num2):
    # do something to num1 and num2
    pass

A = 1
B = 2

import timeit, functools
t = timeit.Timer(functools.partial(foo, A, B)) 
print(t.timeit(5))

EDIT using lambda, thanks @jupiterbjy

we can use lambda function without parameters instead of functools library

def foo(num1, num2):
    # do something to num1 and num2
    pass

A = 1
B = 2

import timeit
t = timeit.Timer(lambda: foo(A, B)) 
print (t.timeit(5))

Here is an example of how to compartmentalize the timing routine, without calling globals

def foo(a, b):
    '''Do something to `a` and `b`'''
    return a + b

def time_foo():
    '''Create timer object simply without using global variables'''
    import timeit

    _foo = foo
    a = 1
    b = 2

    # Get `Timer` oject, alternatively just get time with `timeit.timeit()`
    t = timeit.Timer('_foo(a, b)', globals=locals())

    return t

You could even generalize this if you wanted to use the same timeit function to time other functions. Here is an example with your example main() routine:

def foo1(a, b):
    '''Add `a` and `b`'''
    return a + b

def foo2(a, b):
    '''More math on `a` and `b`'''
    return (a**2 * b)**2

def time_foo(func, **kwargs):
    '''Create timer object simply without using global variables'''
    import timeit
    return timeit.timeit('func(**kwargs)', globals=locals())

def run():
    '''Modify inputs to foo and see affect on execution time'''

    a = 1
    b = 2
    for i in range(10):
        # Update `a` and `b`
        a += 1
        b += 2
        # Pass args to foo as **kwargs dict
        print('foo1 time: ', time_foo(foo1, **{'a':a, 'b':b}))
        print('foo2 time: ', time_foo(foo2, **{'a':a, 'b':b}))

    return None
Answered By: ryanjdillon

I prefer creating a static class with all the Data ready to be picked up prior of running the timer.

Another note, it is better to do test runs in function rather then in the global space, as the global space isn’t taking advantage of FAST_LOAD Why does Python code run faster in a function?

class Data(object):
    """Data Creation"""
    x = [i for i in range(0, 10000)]
    y = tuple([i for i in range(0, 10000)])
    def __init__(self):
        pass

import timeit

def testIterator(x):
    for i in range(10000):
        z = i


print timeit.timeit("testIterator(Data.x)", setup="from __main__ import testIterator, Data", number=50)
print timeit.timeit("testIterator(Data.y)", setup="from __main__ import testIterator, Data", number=50)
Answered By: user1767754

This should work:

import timeit

def f(x,y):
    return x*y

x = 5
y = 7

print(timeit.timeit(stmt='f(x,y)',
                    setup='from __main__ import f, x, y',
                    number=1000))
Answered By: FooBar167

There is a much simpler solution (at least for Python 3), you can cause the code to be executed within your current global namespace:

t = timeit.Timer(stmt="foo(num1,num2)", globals=globals())

https://docs.python.org/3/library/timeit.html#examples
I know globals are not preferred, but if you are just making a quick script to check something I think this is the easiest implementation.

Answered By: RustyToms

I was playing around with timing in Python 3.7 today and trying to pass functions and variables into the timer. This is what I came up with.

import re

text = "This         is      a  test of the      emergency broadcast       system"

def regex(text):
    return re.sub(r"(s)1{1,}", r"1", text)

def loop_while(text):
    if "  " in text:
        while "  " in text:
            text = text.replace("  ", " ")

    return text

if __name__ == "__main__":
    import timeit

    callable_functions = [item for item in locals().items() if callable(item[1])]

    for func_name, func in callable_functions:
        elapsed_time = timeit.timeit(f"{func_name}(text)", globals=globals(), number=100000)
        print(f"{func_name}: {elapsed_time} n{func(text)}n")

This outputs:

regex: 1.378352418
This is a test of the emergency broadcast system

loop_while: 0.15858950299999997
This is a test of the emergency
broadcast system

Then all it takes to test a new version is adding in a new function. Something like:

def split_join(text):
    return " ".join(text.split())

Now it outputs:

regex: 1.378352418
This is a test of the emergency broadcast system

loop_while: 0.15858950299999997
This is a test of the emergency broadcast system

split_join: 0.05700970800000005
This is a test of the emergency broadcast system

Answered By: GollyJer

Another option is to bind the function to its arguments via functools (similar to std::bind). Then you don’t need to pass arguments to timeit, the callable returned by functool.partial takes care of that:

    def findMax(n):#n is an array
        m = 0
        c = 0
        for i in range(len(n)):
            c += 1
            if m < n[i]:
                m = n[i]
        return m, c


import timeit
import functools
a = [6, 2, 9, 3, 7, 4, 5]
t = timeit.Timer(functools.partial(findMax,a))
t.timeit(100)
Answered By: Mahi Kumar

You have to create the variable within the setup string. Here I import the function, and create one of the variables that i pass to it. I also set one of the variables by casting it to the stmt string

SETUP = '''
from __main__ import policy_iteration
from environments.gridworld import GridworldEnv

env = GridworldEnv()
'''

discount = 5
timeit.timeit("policy_iteration(env,discount_factor="+str(discount)+")",
                          setup= SETUP,
                          number=10))
Answered By: PigSpider
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.