Have you ever been exposed to a new set of Python code and you wanted to dive in and explore what the code did and how it worked? Besides the obvious approach of reading the code, one could naively add print statements and execute the script multiple times. In this post I discuss how one can use the debugger to have an interactive shell at specific lines, which is a perfectly valid approach, but today I want to discuss an additional option.

Introducing a native built in flag to the python interpreter, -i, which the man pages describes as:

inspect interactively after running script

For an example, imagine we had this script.py code and pretend that this script had a very complicated function that we couldn’t follow and we wanted to know what the output of that function was. We could add a breakpoint() line at the end of the file, as described in our previously linked blog post, but this file lends itself to be ran to completion and then be able to be inspected.

def long_complicated_function() -> list:
    l = []
    for i in range (10):
        l.append(i)
    return l

output = long_complicated_function()

To do this, we simply run the program with the -i flag:

$ python3 -i script.py

This will drop us into an interactive shell where we can inspect the current state of the program. For example, we could write output and the interpreter will render the value of that variable:

python3 -i script.py
>>> output
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Additionally, we’re able to write new lines of code in this session and get the results out. Imagine we wanted to add one to each value in output:

>>> [i+1 for i in output]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

We can even save this to a new variable and reference it:

>>> my_new_list=[i+1 for i in output]
>>> my_new_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Additionally, we can even import dependencies and leverage them:

>>> import json
>>> json.dumps(my_new_list)
'[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]'

What’s really interesting about this approach is how it can be used in conjunction with Exceptions being raised. Let’s look at this slightly modified script.py.

def long_complicated_function() -> list:
    l = []
    for i in range (10):
        l.append(i)
    return l

raise Exception()
output = long_complicated_function()

Since an uncaught exception being raised is considered program termination, our -i flag will still work, and drop us into an interactive shell once again. Executing this with -i will drop us into an interactive shell just before the output variable is initialized.

$ python3 -i script.py
Traceback (most recent call last):
  File "/home/ghilston/Git/GregHilston/greghilston/static/post/python-interactive-mode-on-exit/script.py", line 7, in <module>
    raise Exception()
Exception
>>>

Python’s interactive mode is incredibly powerful and should be used when experimenting with some unfamiliar code or data to build up one’s knowledge of a system. There’s no replacement for simply reading the code before you, but this approach is much better than executing numerous times with minor print changes.