Reputation: 1099
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
Upvotes: 1
Views: 885
Reputation: 82899
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.
Upvotes: 0
Reputation: 194
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))
Upvotes: 0
Reputation: 194
[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.
Upvotes: 1
Reputation: 2309
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.
Upvotes: 0