[pypy-issue] Issue #1820: IPython timeit magic not working anymore. (pypy/pypy)

Yichao Yu issues-reply at bitbucket.org
Sun Jul 20 09:06:40 CEST 2014

New issue 1820: IPython timeit magic not working anymore.

Yichao Yu:

The following is a copy of the `%timeit` magic in IPython.
It works correctly (with limited function of course) on CPython 2 and 3 but the input statement does not work at all on pypy.

For testing, run `run_timeit("print(1)")`. On CPython, a lot of "1"'s are printed. On pypy, there's no output at all.

PyPy version, latest default 24db6697b691
ArchLinux x86_64, gcc 4.9, `CFLAGS=-O3`, pypy translated with `--shared`,

import timeit, ast, sys

class TimeitTemplateFiller(ast.NodeTransformer):
    """Fill in the AST template. for timing execution.

    This is quite closely tied to the template definition, which is in
    def __init__(self, ast_setup, ast_stmt):
        self.ast_setup = ast_setup
        self.ast_stmt = ast_stmt

    def visit_FunctionDef(self, node):
        "Fill in the setup statement"
        if node.name == "inner":
            node.body[:1] = self.ast_setup.body

        return node

    def visit_For(self, node):
        "Fill in the statement to be timed"
        if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
            node.body = self.ast_stmt.body
        return node

def _format_time(timespan, precision=3):
    """Formats the timespan in a human readable form"""
    import math

    if timespan >= 60.0:
        # we have more than a minute, format that in a human readable form
        # Idea from http://snipplr.com/view/5713/
        parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
        time = []
        leftover = timespan
        for suffix, length in parts:
            value = int(leftover / length)
            if value > 0:
                leftover = leftover % length
                time.append(u'%s%s' % (str(value), suffix))
            if leftover < 1:
        return " ".join(time)

    # Unfortunately the unicode 'micro' symbol can cause problems in
    # certain terminals.
    # See bug: https://bugs.launchpad.net/ipython/+bug/348466
    # Try to prevent crashes by being more secure than it needs to
    # E.g. eclipse is able to print a, but has no sys.stdout.encoding set.
    units = [u"s", u"ms",u'us',"ns"] # the save value
    if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
            units = [u"s", u"ms",u'\xb5s',"ns"]
    scaling = [1, 1e3, 1e6, 1e9]

    if timespan > 0.0:
        order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
        order = 3
    return u"%.*g %s" % (precision, timespan * scaling[order], units[order])

def run_timeit(stmt):
    timefunc = timeit.default_timer
    number = 0
    repeat = timeit.default_repeat
    precision = 3

    timer = timeit.Timer(timer=timefunc)
    # this code has tight coupling to the inner workings of timeit.Timer,
    # but is there a better way to achieve that the code stmt has access
    # to the shell namespace?

    # called as line magic
    ast_setup = ast.parse("pass")
    ast_stmt = ast.parse(stmt)

    # This codestring is taken from timeit.template - we fill it in as an
    # AST, so that we can apply our AST transformations to the user code
    # without affecting the timing code.
    timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
                                    '    setup\n'
                                    '    _t0 = _timer()\n'
                                    '    for _i in _it:\n'
                                    '        stmt\n'
                                    '    _t1 = _timer()\n'
                                    '    return _t1 - _t0\n')

    timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
    timeit_ast = ast.fix_missing_locations(timeit_ast)

    code = compile(timeit_ast, "<magic-timeit>", "exec")

    ns = {}
    exec(code, {}, ns)
    timer.inner = ns["inner"]

    if number == 0:
        # determine number so that 0.2 <= total time < 2.0
        number = 1
        for _ in range(1, 10):
            if timer.timeit(number) >= 0.2:
            number *= 10
    all_runs = timer.repeat(repeat, number)
    best = min(all_runs) / number
    print(u"%d loops, best of %d: %s per loop" % (number, repeat,
                                                  _format_time(best, precision)))

More information about the pypy-issue mailing list