1

I was wondering if there was away to redirect both the input and output of a python script to a file so that they were interlaced. Essentially say I have the following code:

x = 1
print("hello, world")
print(x)
x = 2
print(x)

def myfun(Val):
    return(Val+1)
myfun(7)

I would be looking for the following to be displayed in an output file

x = 1
print("hello, world")
"hello, world"
print(x)
1
x = 2
print(x)    
2

def myfun(Val):
    return(Val+1)
myfun(7)
8

Things that I have already looked at include:

  • simple file redirecting python myfile.py > output.log however this doesn't capture the input

  • The trace module python -m trace myfile.py however this displays way way way too much information and bloats the runtime. I couldn't find any obvious way of limiting this to just the top level code (not every module and function call)

  • Jupyter notebooks - I appreciate that these clearly display input and output however I really want to keep this to command line executable scripts with basic ascii python files.

Ideally I was hoping to find either some sort of bash wizardry with re-directs, a python command line option or a common python module that is able to handle this, but so far no luck :(

EDIT: As a further clarifying example essentially I am trying to re-create the following functionality from R. Say we have the R program myprog.R as:

myfun <- function(x){
    x + 1
}
y <- myfun(7)

print("hello,world!")
print(y)
y + 1

Then running from the command line R CMD BATCH myprog.R produces the file myprog.Rout which looks like

myfun <- function(x){
    x + 1
}
y <- myfun(7)

print("hello,world!")
[1] "hello,world!"

print(y)
[1] 8

y + 1
[1] 9
gowerc
  • 1,039
  • 9
  • 18
  • Why can't you just write the input and output to a file or a CSV file? – SPYBUG96 Jun 05 '18 at 20:36
  • That would be ideal. Do you have recommendations on which commands to use which will enable the two to be interlaced ? – gowerc Jun 05 '18 at 20:38
  • I'm a little confused with what you want to do, by input and output do you mean input and output to each function you created? – SPYBUG96 Jun 05 '18 at 20:42
  • You could just open an interactive Python terminal and paste your code. Code lines will be prefixed with `>>>` but IMHO that clearer, anyway. – tobias_k Jun 05 '18 at 20:58
  • I agree that having it look like the interactive terminal would make it much more readable, but it all depends on your ultimate goal you want to achieve. – d parolin Jun 05 '18 at 21:11

4 Answers4

1

[EDIT] This is a very limited script, unfortunately this breaks immediately if your code gets more complex (means statements span multiple lines), like as simple as having a function.

For your very simple example usecase you can use an intermediate script as follows (lets assume filename rt.py):

import sys

in_file = sys.argv[1]

with open(in_file) as script:
    for line in script:
        line = line.strip()
        if line:
            # Option to visually separate it by using repr
            # print(repr(line))
            print(line)
            # Another optional use a prefix
            # print(">>>", end="")
            exec(line)

Now you can pass it your existing script

python rt.py somescript.py

It would not be impossible to enhance this to preparse the script based on indentation , but it is probably not worthwhile and I hope other solutions exist that I am not aware of.

d parolin
  • 194
  • 6
  • It does not, thus I stated it will break with functions, which is basically because of the multiple lines. Will move this hint to the top. Have an alternative using trace and cutting the output which I will post in a second. – d parolin Jun 05 '18 at 21:02
  • Hi, Thank you for your answer, apologies though my example was deceptively simple, indeed I would be looking for my python scripts to be more complex including creating my own objects and functions. I have updated the initial question to try and clarify this a bit more. – gowerc Jun 05 '18 at 22:02
  • Yeah I figured that would be the case, given your new input my second "solution" would not exactly print the function definitions as they are not contained in the trace module's output either. Intrigued by the comments from @tobias_k I played with tty as input to the interpreter, but not sure if you could live with the >>> and ... output – d parolin Jun 05 '18 at 22:18
0

Assuming the input file is called a.py, this redirects stdout of the eval call so that it can be caught then written to a file (out.py) along with the source.

Like dparolin said, although this works for easy scripts as soon as you have blocks things will fall apart.

import sys
from io import StringIO
import contextlib


filename="a.py"
f= open(filename,"r")
f_out= open("out.py","w+")

SCRIPT_LOCALS={}

@contextlib.contextmanager
def stdoutIO(stdout=None):
    old = sys.stdout
    if stdout is None:
        stdout = StringIO()
    sys.stdout = stdout
    yield stdout
    sys.stdout = old

def eval_with_return(str_code):
    with stdoutIO() as s:
        exec(str_code,SCRIPT_LOCALS)
        return s.getvalue()


for line in f:
    orig_line=line if line.endswith("\n") else line+"\n"
    sys_out_res=eval_with_return(line)
    f_out.write(orig_line)
    f_out.write(sys_out_res)

f.close()
f_out.close()

Scoping came from here. Context manager redirect came from here.

code11
  • 1,986
  • 5
  • 29
  • 37
0

As an alternative you could parse the output created by the trace module and cut off the pieces you do not like. In this example I limited myself to the prefixes and the module printouts. Be cautious as without properly formatted (PEP8) code this will easily cut away function definitions too, however that may be a feature not a bug ;-) In its default use trace does not give you the function contents however. Depends on what exactly you need.

Save the following as trace_rt.py and use as follows

python -m trace -t yourfile.py | trace_rt.py

trace_rt.py:

import sys


def cutoff(in_line):
    """Removes trace prefix if present"""
    if "): " in line:
        return line.split("): ")[1]
    elif line.startswith("--- modulename:"):
        return ""
    return line


for line in sys.stdin:
    line = line.strip()
    if line:
        print(cutoff(line))
d parolin
  • 194
  • 6
0

One rather simple way would be to just paste the code into an interactive Python shell, i.e. open your file, copy the content, run python without a file argument, and paste the content.

Example file:

def foo(n):
    if n % 2 == 0:
        return n**2
    else:
        return n**.5

x = 41
print(foo(x))
print(foo(2 * x))

Output:

>>> def foo(n):
...     if n % 2 == 0:
...         return n**2
...     else:
...         return n**.5
... 
>>> x = 41
>>> print(foo(x))
6.4031242374328485
>>> print(foo(2 * x))
6724

This adds those >>> and ... prefixes to the code lines, but this might actually help readability of the output. Also, code-files have to be indented with spaces (no tabs), and blocks (like function definitions) have to be terminated with an extra blank line, otherwise they won't work.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • Addendum: There's probably also a way to do this without "manual" copy-paste, but with pipes or a special Python flag, but I did not find any. If you know how, please comment. – tobias_k Jun 05 '18 at 21:15
  • I was intrigued by the approach to feed stdin into the interpreter, supposedly it is somewhat possible with using pseudo TTY, but I have not been able to conjure a working example. https://stackoverflow.com/questions/3535881/is-there-a-way-to-send-data-to-another-processs-standard-input-under-mac-os – d parolin Jun 05 '18 at 22:20
  • Dabbled with subprocess, I suppose it is possible with stdin an stdout to get this to work, I couldn't quite yet – d parolin Jun 05 '18 at 23:49