Emulating the python interactive console
Yesterday and today I had students in my CS470/570 AI class ask me about how we were supposed to get python output like
>>> add(1, 2)
3
>>> add(3, 4)
7
You can use the python
command to start up the REPL, then paste your
code, and you should get output as above.
Almost. By default there is no newline before the prompt, but you can get that by resetting sys.ps1,
import sys
sys.ps1 = "\n>>>"
But is there a way to run a python script such that the prompt, input, and output are printed? (without having to copy-paste the code into the interactive REPL)
In R this is easy, just run R
on the command line, redirect an R
script to its standard input, and set the prompt
option in that
script:
$ R --quiet --vanilla < add.R
> options(prompt="\n> ")
> add <- function(x,y){
+ x+y
+ }
> add(1,2)
[1] 3
> add(3,4)
[1] 7
I was thinking there would be a python command line option which would allow this easily, but I did not find one. Instead I hacked my own solution which I called interpreter.py.
It uses the
code
module, which provides all sorts of functionality related to the REPL,
including the InteractiveConsole
class. Docs for its
raw_input
method mention that “The base implementation reads from sys.stdin; a
subclass may replace this with a different implementation.” That
suggested to me that we can define a subclass which takes raw_input
from a python script on disk rather than from stdin.
The raw_input
method was not super well documented so I had to look
at the source code and do some trial and error, but I eventually
figured out that
- it should read one line of code (InteractiveConsole reads from stdin, my subclass reads from a text file on disk).
- if there is nothing else to read (no more lines of code) then
EOFError
should be raised. - it should print the prompt as well as the code.
- it should return a string, line of code with no newline at the end.
That results in the new subclass,
import code
class FileConsole(code.InteractiveConsole):
"""Emulate python console but use file instead of stdin"""
def raw_input(self, prompt):
line = f.readline()
if line=="":
raise EOFError()
print(prompt, line.replace("\n", ""))
return line
Note in the code above we assume that f
is the file handle from
which we want to read lines. To use this class to get the desired
output, we can use the code below:
import sys
sys.ps1 = "\n>>>"
f = open(sys.argv[1])
FileConsole().interact(banner="", exitmsg="")
The code above first adds a newline to the prompt, then opens the file
specified as the first command line argument, then instantiates a
FileConsole
and calls its interact
method (which repeatedly calls
raw_input
until EOFError
is raised). The banner
and exitmsg
arguments specify that nothing should be printed at the start and end
of the console.
Now assume we have a python script file add.py
as below that we
would like to process just as if we copied its code and pasted it into
the python interpreter:
def add(x, y):
return x + y
add(1, 2)
add(3, 4)
The result of running the above add.py
script through
interpreter.py
is
$ python interpreter.py add.py
>>> def add(x, y):
... return x + y
>>> add(1, 2)
3
>>> add(3, 4)
7
And actually this is more flexible than the copy-paste method because
that results in a SyntaxError
because in the add.py
script there
is no empty line after the function definition,
$ python
Python 3.7.6 (default, Jan 8 2020, 19:59:22)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def add(x, y):
... return x + y
... add(1, 2)
File "<stdin>", line 3
add(1, 2)
^
SyntaxError: invalid syntax
>>> add(3, 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'add' is not defined
Overall this interpreter.py python script seems pretty complicated compared to the R solution — is there an easier way to do this in python?
UPDATE 9 Feb 2021
The original post correctly described that the raw_input
method
should return a string without a newline, but my code did not! The
fixed class looks like:
import code
class FileConsole(code.InteractiveConsole):
"""Emulate python console but use file instead of stdin"""
def raw_input(self, prompt):
line = f.readline()
if line=="":
raise EOFError()
no_newline = line.replace("\n", "")
print(prompt, no_newline, sep="")
return no_newline
Also note above the subtle difference that no separator is printed between the prompt and command.
The updated code which calls this class has an additional space in the
definition of sys.ps1
as shown below:
import sys
sys.ps1 = "\n>>> "
f = open(sys.argv[1])
FileConsole().interact(banner="", exitmsg="")
The result looks like
(base) tdhock@maude-MacBookPro:~/teaching/cs470-570-spring-2021$ python interpreter.py test.py
>>> def add(x, y):
... s = x + y
... return s
...
>>> add(1, 2)
3
>>> add(3, 4)
7
which is essentially the same as pasting the code into the python console,
$ python
Python 3.7.6 (default, Jan 8 2020, 19:59:22)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.ps1="\n>>> "
>>> def add(x, y):
... s = x + y
... return s
...
>>> add(1, 2)
3
>>> add(3, 4)
7