5

I would like to write multiple functions in the same Python module, each of which is a separate profiling test using timeit, so that I can use command line argument to specify which one to run. A naive example (profiling.py) would be:

import sys
import timeit

def foo():

    setup = """
    import random
    """

    foo_1 = """
    for i in range(1000):
        random.randint(0, 99) + random.randint(0, 99)
    """

    foo_2 = """
    for i in range(1000):
        random.randint(0, 99) + random.randint(0, 99)
    """

    foo_3 = """
    for i in range(1000):
        random.randint(0, 99) + random.randint(0, 99)
    """

    print 'foo_1', timeit.Timer(foo_1, setup).timeit(1000)
    print 'foo_2', timeit.Timer(foo_2, setup).timeit(1000)
    print 'foo_3', timeit.Timer(foo_3, setup).timeit(1000)

if __name__ == '__main__':
    if (len(sys.argv) > 1):
        if (sys.argv[1] == 'foo'):
            foo()
    else:
        print 'Which profiling do you want to run?'
        print 'available:'
        print '    foo'

However, when I try python profiling.py foo, I get error like the following:

foo_1
Traceback (most recent call last):
  File "profiling.py", line 32, in <module>
    foo()
  File "profiling.py", line 25, in foo
    print 'foo_1', timeit.Timer(foo_1, setup).timeit(1000)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/timeit.py", line 136, in __init__
    code = compile(src, dummy_src_name, "exec")
  File "<timeit-src>", line 6
    _t0 = _timer()
                 ^
IndentationError: unindent does not match any outer indentation level

I have searched for the usual space/tab indention error in the code, but didn't find any. Hence I wonder if it is because I wrap a timeit test inside a function, and this is not allowed?

MLister
  • 10,022
  • 18
  • 64
  • 92
  • 1
    What happens if you remove the whitespace from the strings you are passing to timeit? (e.g. `foo_1="for i in range(1000):\n random.randint(0, 99) + random.randint(0, 99)"`) – mgilson Jun 04 '12 at 19:31
  • Did you by any chance copy and paste that line? A lot of the times when you copy and paste you will encounter that error due to mismatched tab sizes or tabs as white-spaces. – Florin Stingaciu Jun 04 '12 at 19:34

2 Answers2

7

This works:

import sys
import timeit

def foo():

    setup = """
import random
"""

    foo_1 = """
for i in range(1000):
    random.randint(0, 99) + random.randint(0, 99)
"""

    foo_2 = """
for i in range(1000):
    random.randint(0, 99) + random.randint(0, 99)
"""

    foo_3 = """
for i in range(1000):
    random.randint(0, 99) + random.randint(0, 99)
"""

    print 'foo_1', timeit.Timer(foo_1, setup).timeit(1000)
    print 'foo_2', timeit.Timer(foo_2, setup).timeit(1000)
    print 'foo_3', timeit.Timer(foo_3, setup).timeit(1000)

if __name__ == '__main__':
    if (len(sys.argv) > 1):
        if (sys.argv[1] == 'foo'):
            foo()
    else:
        print 'Which profiling do you want to run?'
        print 'available:'
        print '    foo'

The problem is that your strings that you're passing as setup and foo_1, etc. are indented since you've lined them up with your indented code inside the function. However, when that string gets executed by timeit, it raises the indentation error you see because the code should not be indented. This is essentially the same thing that would happen if you tried...

exec("    import sys")

as the first thing in your interactive interpreter session.

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • thanks for the suggestion. So I can fix the error by doing `setup = """import random"""`(all in one line), **without** making any change to foo_1. I wonder why? – MLister Jun 04 '12 at 19:43
  • `timeit` must do some "cleanup" on the argument to be run, but not on the setup-code. – mgilson Jun 04 '12 at 19:45
  • @MLister What's funny about that? You don't have any indentation in front of your setup code -- only a newline (blank lines are perfectly acceptable in python). – mgilson Jun 04 '12 at 19:56
  • ooops, sorry I misunderstood your point. thanks for pointing out. – MLister Jun 04 '12 at 19:59
0

mgilson gave a great answer. But personally, I find the visually-dedented lines in the heredoc strings distracting. It also breaks the vim python code folding I use.

You can get the same effect by writing the lines separately, and joining them, like so:

import sys
import timeit


def foo():

    setup = 'import random'

    foo_1 = '\n'.join([
        'for i in range(1000):',
        '    random.randint(0, 99) + random.randint(0, 99)',
    ])

    foo_2 = '\n'.join([
        'for i in range(1000):',
        '    random.randint(0, 99) + random.randint(0, 99)',
    ])

    foo_3 = '\n'.join([
        'for i in range(1000):',
        '    random.randint(0, 99) + random.randint(0, 99)',
    ])

    print 'foo_1', timeit.Timer(foo_1, setup).timeit(1000)
    print 'foo_2', timeit.Timer(foo_2, setup).timeit(1000)
    print 'foo_3', timeit.Timer(foo_3, setup).timeit(1000)

if __name__ == '__main__':
    if (len(sys.argv) > 1):
        if (sys.argv[1] == 'foo'):
            foo()
    else:
        print 'Which profiling do you want to run?'
        print 'available:'
        print '    foo'

You get less space on each line, but I the tradeoff is worth it for me.

Chip Hogg
  • 1,570
  • 13
  • 14