4

I have a file containing Python statements, and I'd like to run Python in such a way that it prints to stdout what would be shown if those commands were run in the REPL.

For example, if the file is

1 + 4
'a' + 'b'

Then the output should be

>>> 1 + 4
5
>>> 'a' + 'b'
'ab'

Is there a way to do this?

2
  • 1
    Windows ? nx ? Cross-platform ? Commented Aug 28, 2014 at 21:05
  • Linux, though I'd really hope there'd be a cross-platform approach. Commented Aug 28, 2014 at 21:25

5 Answers 5

4

You can use replwrap from pexpect to achieve this goal, even has a python method:

from pexpect import replwrap

with open("commands.txt", "r") as f:
    commands = [command.strip() for command in f.readlines()]

repl = replwrap.python()
for command in commands:
   print ">>>", command
   print repl.run_command(command),

Which returns:

python replgo.py 
>>> 1 + 4
5
>>> 'a' + 'b'
'ab'

You'll need to get the latest version of pexpect.

Sign up to request clarification or add additional context in comments.

1 Comment

This should be replaced by the newer, simpler and self contained answer stackoverflow.com/a/67511928/1950432 It requires no third party libraries, and should work almost anywhere you have python interpreter and a unix shell.
4

(not so) quick and (mostly) dirty, using the code module:

import sys
import code

infile = open('cmd.py')
def readcmd(prompt):
    line = infile.readline()
    if not line:
        sys.exit(0)

    print prompt,line.rstrip()
    return line.rstrip()

code.interact(readfunc=readcmd)

Lots of room for improvement, but it's late here. Anyway, example:

sh$ cat cmd.py
1 + 4
'a' + 'b'

1/0

def f(x):
    return x*2

f(3)
sh$ python console.py 
Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>  1 + 4
5
>>>  'a' + 'b'
'ab'
>>>  
>>>  1/0
Traceback (most recent call last):
  File "<console>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>>  
>>>  def f(x):
...      return x*2
...  
>>>  f(3)
6

Comments

3

Some ast magic can help here:

import ast
import itertools


def main():
    with open('test.txt', 'r') as sr:
        parsed = ast.parse(sr.read())
        sr.seek(0)
        globals_ = {}
        locals_ = {}
        prev_lineno = 0
        for node in ast.iter_child_nodes(parsed):
            source = '\n'.join(itertools.islice(sr, 0, node.lineno - prev_lineno))[:-1]
            print('>>> {}'.format(source))
            if isinstance(node, ast.Expr):
                print(eval(source, globals_, locals_))
            else:
                exec(source, globals_, locals_)
            prev_lineno = node.lineno

if __name__ == '__main__':
    main()

Input:

1 + 4
'a' + 'b'
a = 1
a

Output:

>>> 1 + 4
5
>>> 'a' + 'b'
ab
>>> a = 1
>>> a
1

What this does is to find the start and end line numbers of each individual statement by parsing the source using the ast module, then calling either eval or exec depending on whether it was a statement or an expression.

The context is saved in globals_ and locals_.

You can possibly make this safer by using some python sandbox to perform the eval and exec.

Comments

1

You can pipe your input to pythons "code" module. It will show the output, but won't show the input.

$ echo '1 + 1' | python -m code
Python 2.7.10 (v2.7.10:15c95b7d81dc, May 23 2015, 09:33:12) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 2

Comments

1

We can do this by tricking the Python command into starting an interactive session. This can be done with unbuffer, usually provided alongside the expect tool in Linux distros. This approach is pretty general, and will work for all sorts of programs which behave differently when they're called interactively.

The following command will start a REPL, even though the (currently empty) input is coming from a pipe:

$ printf '' | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

A couple of annoying behaviours make this work a little differently to an interactive session. Firstly, unbuffer will exit as soon as it encounters EOF, so we need a small delay to ensure Python has enough time to start up:

$ (sleep 1; printf '') | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Notice that this time we managed to get a >>> prompt.

Secondly, our input commands won't get echoed as part of the output. For example:

$ (sleep 1; printf 'print("a" * 10)\n'; sleep 1) | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> aaaaaaaaaa
>>>

Notice that our input print("a" * 10)\n doesn't appear after the >>> prompt, even though the result does get printed.

We can fix this using tee to duplicate our commands to both stdout and to the REPL (which we execute using process substitution):

$ (sleep 1; printf 'print("a" * 10)\n'; sleep 1) | tee >(unbuffer -p python3)
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("a" * 10)
aaaaaaaaaa
>>>

This seems to behave properly, but we have to put delays between each line. Here's a script which will do that automatically, reading lines from stdin:

#!/usr/bin/env bash
set -e

cat | (sleep 1; while IFS='' read -r LINE
do
 sleep 0.2
 echo "$LINE"
done; sleep 1) | tee >(unbuffer -p python3)

This seems to do the job (I'm using printf but this would work equally well with a separate file; note that the REPL needs two newlines to execute an indented block, just like in an interactive session):

$ printf 'if True:\n  print("hello")\nelse:\n  print("world")\n\n12345\n' | ./repl.sh
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> if True:
...   print("hello")
... else:
...   print("world")
...
hello
>>> 12345
12345
>>>

We can pipe this through standard text processing tools like head and tail if you want to remove the final >>> and the startup noise, e.g.

$ printf 'if True:\n  print("hello")\nelse:\n  print("world")\n\n12345\n' | ./repl.sh | tail -n+4 | head -n-1
>>> if True:
...   print("hello")
... else:
...   print("world")
...
hello
>>> 12345
12345

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.