python/sage: how to get the last expression?

Question:

If I do in Jupiter (in both python and sage) something like:

a = 42
b = 43
a + b

it will, somehow, manage to understand that this process returns the value a + b, i.e. 85 here:

enter image description here

Similarly, if I just do:

a = 42

it will understand that there is nothing to return.

I would like now to do something similar for a different application (to cache the result of a python operation)… how could I get this information, ideally by just running the code and appending some python code to obtain this information? I tried to do:

a = 42
b = 43
a + b
print(_)

but this fails. I was thinking to do something stupid like adding res = in front of the last line, but it might fail for instance if the last line is indented etc… How can I elegantly obtain this information?

Asked By: tobiasBora

||

Answers:

Using ipython, there are various ways of referencing a previous out line:

In [113]: a = 42
     ...: b = 43
     ...: a + b
Out[113]: 85

In [114]: _     # also __ and ___
Out[114]: 85

In [115]: _113
Out[115]: 85

In [116]: Out[113]
Out[116]: 85

Out is a list of values

There isn’t a way of referencing an unassigned line in a cell, as you want:

a = 42
b = 43
a + b
print(?)

Using a+b; will also surpress the last display/save, same as doing c = a+b.

_ also works in a plain interactive python session. jupyter/ipython enhances that history collection.

Answered By: hpaulj

I think this was already asked as part of a post entitled ‘Get value and type of python variable similar to Jupyter behavior’ where the OP had already made significant progress towards getting to the solution.

The resulting approach is to use Python exec() to first run the code in the lines and then use Python eval() to evaluate the last expression, like Jupyter does with the default setting for InteractiveShell.ast_node_interactivity.

I’ll refine* & transpose my answer from there for the specific code situation here:

lines = """a = 42
b = 43
a + b"""

exec(lines,globals(),locals())
print(eval(lines.split("n")[-1]))

That prints the result of 85 as expected.

The OP points out a concern about indentation. Indeed, I do note that my present proposed solution will fail though if the last line is indented. This could be addressed, I believe. If the last line has any degree of indentation I can collect the lines prior back until the level of indenting is the outer level. Then use those collected lines as ‘the last expression’ and evaluate it. I haven’t done that here though because the solution without implementing that part is fairly clear and I didn’t want to make it more abstruse with the addition of that yet.

Make sure to read the warning about exec() and eval() over where I had originally posted this approach.

In response to a comment, I’ll also highlight in the answer you can further make things more feature rich or combine in other ways to bring the Jupyter/IPython abilities into Python because you can do imports. At my answer in the thread ‘How do I construct a python function where the input in python code and output is ipython rich output (in HTML)?’, I have the section ‘UPDATE IN RESPONSE TO FIRST COMMENT:’ where I talk about using IPython as an import in Python to capture RichOutput, with that approach further fleshed out in a demo that goes into some acrobatics you can do utilizing methods and functions from IPython in Jupyter.

*(I did end up refining my old answer at the other place prompted by transposing it to here. Revisiting the answer I had there uncovered an issue were I wasn’t splitting right to get the actual last line for evaluating. I just got lucky last time that the last line in my original answer was one character & so the last line and last character were the same in the original case. I now fixed it there, too.)

Answered By: Wayne
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.