0

I'd like to get the text from the console using python when using the exec function of a string as well as the line in the script that executed it

I have the following python script as a string, which I evaluate using exec

python_string = """
result_one = len([1,2,3])
print result_one
result_two = range(r)
print result_two
result_three = max(result_two)
print "done"
"""
exec(python_string)

This returns the following in the console:

3
[0, 1, 2]
done

I'd like to attain the text within the console as it executes. In other words, I'd like the following three strings returned, since they appeared in the console

'3'
'[0, 1, 2]'
'done'

In addition, I'd like to attain each line of the python_string only as it executes, so I should see each of the below lines, once its been executed

result_one = len([1,2,3])
print result_one
result_two = range(r)
print result_two
result_three = max(result_two)
print "done"

I believe to accomplish this I need to attain the relevant frame within the call stack, which I've been reading about here: What is the difference between a stack and a frame?

I tried to accomplish this with the below code with little success:

import sys
import inspect

python_string_as_dictionary = dict(enumerate(open(inspect.getfile(inspect.currentframe()), 'r+').read().split("\n")))
#>>{0: 'import sys', 1: 'import inspect', 2: '', 3: 'f_dict = dict(enumerate(open(inspect.getfile(inspect.currentframe()), \'r+\').read().split("\\n")))', 4: 'print f_dict', 5: 'class SetTrace(object):', 6: '    def __init__(self, func):', 7: '        self.func = func', 8: '', 9: '    def __enter__(self):', 10: '        sys.settrace(self.func)', 11: '        return self', 12: '', 13: '    def __exit__(self, ext_type, exc_value, traceback):', 14: '        sys.settrace(None)', 15: '', 16: '', 17: 'def monitor(frame, event, arg):', 18: '\tprint event', 19: '\tprint "line: {}".format(str(frame.f_lineno))', 20: '\tif event == "line" :', 21: '\t\treturn monitor', 22: '', 23: 'python_string = """', 24: 'result_one = len([1,2,3])', 25: 'print result_one', 26: 'result_two = range(result_one)', 27: 'print result_two', 28: 'result_three = max(result_two)', 29: 'print "done"', 30: '"""', 31: 'with SetTrace(monitor):', 32: '    exec(python_string)', 33: '', 34: '', 35: ''}

class SetTrace(object):
    def __init__(self, func):
        self.func = func

    def __enter__(self):
        sys.settrace(self.func)
        return self

    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)


def monitor(frame, event, arg):
    print "line: {}".format(str(frame.f_lineno))
    print python_string_as_dictionary[frame.f_lineno-1].strip()
    if event == "line" :
        return monitor

python_string = """
result_one = len([1,2,3])
print result_one
result_two = range(result_one)
print result_two
result_three = max(result_two)
print "done"
"""
with SetTrace(monitor):
    exec(python_string)
Community
  • 1
  • 1
Chris
  • 5,444
  • 16
  • 63
  • 119
  • 3 and [0, 1, 2] are in result_one and result_two respectively which you can access. you need to assign 'done' into a variable for access, such as done = "done". – Shiping Apr 23 '17 at 22:56
  • Are these programs self-contained? You could save them to temporary files and execute those. Do it with `subprocess.Popen` and you can read the output lines as they are generated. – tdelaney Apr 23 '17 at 22:58
  • Utilizing subprocess may just do the trick! I'll try to do that. – Chris Apr 23 '17 at 23:05
  • BTW, your string example has a bug and doesn't produce the output you show. Fix that so we can see if we did it right. – tdelaney Apr 23 '17 at 23:11

1 Answers1

1

You can run another copy of python as a subprocess and pipe in the program you want to run. The - parameter tells python to read the program from stdin and run it when it sees EOF.

import subprocess as subp

python_string = """
result_one = len([1,2,3])
print result_one
result_two = range(result_one)
print result_two
result_three = max(result_two)
print result_three
print "done"
"""

proc = subp.Popen(['python', '-'], stdin=subp.PIPE,
    stdout=subp.PIPE, stderr=subp.STDOUT)
proc.stdin.write(python_string)
proc.stdin.close()
for line in proc.stdout:
    print 'out: {}'.format(line.strip())
proc.wait()
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • Is there anyway to print the actual lines within the python_string as well, so that the lines are shown before the result is outputted. This may be a different question, but just figured Id ask here in case. This is what I mean by that output. result_one = len([1,2,3]) print result_one "out: 3" result_two = range(result_one) print result_two "out: [0, 1, 2]" result_three = max(result_two) print result_three "out: 2" print "done" "out: done" – Chris Apr 24 '17 at 02:05
  • So, interleave the code and its output? I'm not sure. There are profiling packages out there that may do what you want. – tdelaney Apr 24 '17 at 03:32
  • Yep - essentially. Got it - I'll look into it more and thanks again! – Chris Apr 24 '17 at 05:59