75

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!

CppLearner
  • 16,273
  • 32
  • 108
  • 163
  • Well first of all, the indentation here is confusing. It looks like `foo` is being defined in a different scope than `t`... – senderle Feb 23 '11 at 02:42
  • hi. i fixed the indentation. does it look better now? :] thanks. – CppLearner Feb 23 '11 at 02:45
  • timeit is ok for one-liners but for anything else better check this method http://stackoverflow.com/questions/5478351/python-time-measure-function – vincent Jan 08 '14 at 23:10
  • There is a very simple solution for this in python3, you can just run the expression in the global namespace by adding the `globals=globals()` parameter to the timeit call: https://stackoverflow.com/a/51913199/2925963 – RustyToms Aug 18 '18 at 23:15
  • **TL;DR**: `duration,return_value= timeit.Timer(functools.partial(your_function_name, argument_1, argument_2,argument_3)).timeit(1)` where your_function name is `foo` if you write a function like: `def foo(argument_1, argument_2, argument_3):`. Also include the: `timeit.template = """..` block from @Hugh Bothwell. And the `return_value` is what the function returns, for example `argument_1 + argument_2`. And use: `import timeit, functools`. This solution is given by @Jose Ricardo Bustos M. – a.t. Jan 17 '23 at 14:57

12 Answers12

94

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))
Jose Ricardo Bustos M.
  • 8,016
  • 6
  • 40
  • 62
  • 3
    Thanks, functools helped me avoid globals, nasty string approach, and the decorator/closure business. – jatal Apr 24 '16 at 17:53
  • This snippet works but with real function I get `ValueError: stmt is neither a string nor callable` (Python 3.6). My function works normally without timeit. – Peter.k Feb 14 '19 at 16:30
  • 1
    @Peter.k That is because if you want to just use `timeit.timeit()` it needs to be fed your function as a string for example `timeit.timeit('foo')` – 13ros27 Mar 19 '19 at 12:55
  • 3
    Thanks, this is exactly what I was looking for! Should also note that you don't even need to instantiate a `Timer`; this works also: ```timeit.timeit(functools.partial(foo, A, B), number=5)``` – typeracer Mar 21 '19 at 05:14
  • 2
    For current CPython3.8, would calling ```lambda: foo(A, B)``` be bad idea compared to ```functools.partial(foo, A, B)``` for ```timeit```? – jupiterbjy Nov 07 '20 at 03:04
  • 1
    @jupiterbjy you are right, you can use function lambda instead of functools library – Jose Ricardo Bustos M. Nov 10 '20 at 17:19
  • @jupiterbjy [What is the difference between a 'closure' and a 'lambda'?](https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda) – Jose Ricardo Bustos M. Nov 10 '20 at 22:54
  • Didn't know ```partial``` creates closure - so it's really just up to preferences, I see. Good to know. – jupiterbjy Nov 11 '20 at 13:41
19

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.

Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
15

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)
Lucas S.
  • 13,391
  • 8
  • 46
  • 46
12

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"))
  • 1
    works only if `v1` and `v2` are globals. If they are local to the f function does not work – Kyrol Aug 20 '16 at 17:39
9

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)
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
6

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
RustyToms
  • 7,600
  • 1
  • 27
  • 36
6

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)
stfn
  • 647
  • 5
  • 9
Mahi Kumar
  • 91
  • 1
  • 1
  • To me this is by far the simplest solution. It also works with class functions =) Any drawbacks/side-effects? – stfn Feb 12 '20 at 10:59
3

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
ryanjdillon
  • 17,658
  • 9
  • 85
  • 110
1

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))
FooBar167
  • 2,721
  • 1
  • 26
  • 37
0

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)
user1767754
  • 23,311
  • 18
  • 141
  • 164
0

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

GollyJer
  • 23,857
  • 16
  • 106
  • 174
0

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))
PigSpider
  • 881
  • 9
  • 18