jakevdp jakevdp - 2 months ago 15
Python Question

Python: exec() a code block and eval() the last line

I have a string literal containing one or more lines of (trusted) Python code, and I would like to

exec()
the block, while capturing the results of the last line. More concretely, I would like a function
exec_then_eval
that returns the following:

code = """
x = 4
y = 5
x + y
"""

assert exec_then_eval(code) == 9


A couple things I've tried:


  1. By splitting off the last line, you can exec the first block, then eval the last line; e.g.

    def exec_then_eval(code):
    first_block = '\n'.join(code.splitlines()[:-1])
    last_line = code.splitlines()[-1]
    globals = {}
    locals = {}
    exec(first_block, globals, locals)
    return eval(last_line, globals, locals)


    This works, but will fail if the last statement has multiple lines.

  2. If the code itself is modified so the result is stored as a local variable, this variable can then be recovered; e.g.

    code = """
    x = 4
    y = 5
    z = x + y
    """

    globals = {}
    locals = {}
    exec(code, globals, locals)
    assert locals['z'] == 9


    again, this works, but only if you're able to first parse the code block in a general way and modify it appropriately.



Is there an easy way to write a general
exec_and_eval
function?

Answer

You could use ast to find the location of the last expression in a code block, divide the code into two parts, and execute them separately.

import ast

def exec_then_eval(code):
    tree, lines = ast.parse(code), code.splitlines()
    # Assuming the last node is an expression
    *stmt, expr = ast.iter_child_nodes(tree)
    # Note: .lineno is 1-indexed
    line, col, _globals, _locals = expr.lineno - 1, expr.col_offset, {}, {}
    # Split at `line` and `col`
    ex = lines[:line] + [lines[line][:col]]
    ev = [lines[line][col:]] + lines[line + 1:]
    exec('\n'.join(ex), _globals, _locals)
    return eval('\n'.join(ev), _globals, _locals)

Some test cases:

exec_then_eval('''x = 4
y = 5
x + y''')) # 9

exec_then_eval('''x = 4
y = 5;x + y''')) # 9

exec_then_eval('''x = 4
y = 5;(
x + y *

2)''') # 14
Comments