python: get the print output in an exec statement

34,018

Solution 1

Since Python 3.4 there is a solution is the stdlib: https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout

from io import StringIO
from contextlib import redirect_stdout

f = StringIO()
with redirect_stdout(f):
    help(pow)
s = f.getvalue()

In older versions you can write a context manager to handle replacing stdout:

import sys
from io import StringIO
import contextlib

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

code = """
i = [0,1,2]
for j in i :
    print j
"""
with stdoutIO() as s:
    exec(code)

print("out:", s.getvalue())

Solution 2

You can redirect the standard output to a string for the duration of the exec call:

Python2

import sys
from cStringIO import StringIO

code = """
i = [0,1,2]
for j in i:
    print(j)
"""

old_stdout = sys.stdout
redirected_output = sys.stdout = StringIO()
exec(code)
sys.stdout = old_stdout

print(redirected_output.getvalue())

Python3

import sys
from io import StringIO

code = """
i = [0,1,2]
for j in i:
    print(j)
"""

old_stdout = sys.stdout
redirected_output = sys.stdout = StringIO()
exec(code)
sys.stdout = old_stdout

print(redirected_output.getvalue())

Solution 3

Here is Py3-friendly version of @Jochen's answer. I also added try-except clause to recover in case of errors in the code.

import sys
from io import StringIO
import contextlib

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

code = """
i = [0,1,2]
for j in i :
    print(j)
"""
with stdoutIO() as s:
    try:
        exec(code)
    except:
        print("Something wrong with the code")
print("out:", s.getvalue())

Solution 4

Here is a small correction of Frédéric's answer. We need to handle a possible exception in exec() to return back normal stdout. Otherwise we could not see farther print outputs:

code = """
i = [0,1,2]
for j in i :
print j
"""

from cStringIO import StringIO
old_stdout = sys.stdout
redirected_output = sys.stdout = StringIO()
try:
    exec(code)
except:
    raise 
finally: # !
    sys.stdout = old_stdout # !

print redirected_output.getvalue()
...
print 'Hello, World!' # now we see it in case of the exception above

Solution 5

Python 3: Get the output of the exec into a variable

import io, sys
print(sys.version)

#keep a named handle on the prior stdout 
old_stdout = sys.stdout 
#keep a named handle on io.StringIO() buffer 
new_stdout = io.StringIO() 
#Redirect python stdout into the builtin io.StringIO() buffer 
sys.stdout = new_stdout 

#variable contains python code referencing external memory
mycode = """print( local_variable + 5 )""" 

local_variable = 2
exec(mycode)

#stdout from mycode is read into a variable
result = sys.stdout.getvalue().strip()

#put stdout back to normal 
sys.stdout = old_stdout 
 
print("result of mycode is: '" + str(result) + "'") 

Prints:

3.4.8
result of mycode is: '7'

Also a reminder that python exec(...) is evil and bad because 1. It makes your code into unreadable goto-spaghetti. 2. Introduces end-user code injection opportunities, and 3. Throws the exception stacktrace into chaos because exec is made of threads and threads are bad mmkay.

Share:
34,018
Bussiere
Author by

Bussiere

Updated on July 09, 2022

Comments

  • Bussiere
    Bussiere almost 2 years

    I want to get the output of an exec(...) Here is my code:

    code = """
    i = [0,1,2]
    for j in i :
        print j
    """
    result = exec(code)
    

    How could I get the things that print outputed? How can I get something like:

    0
    1
    2
    

    Regards and thanks.

  • Bussiere
    Bussiere over 13 years
    i've got a :File "D:\Documents\perso\dev\meta\Server.py", line 77, in decompress_html with self.stdoutIO() as s: AttributeError: exit
  • Bussiere
    Bussiere over 13 years
    i've got a : codeproc = subprocess.Popen(command, stdout=subprocess.PIPE) File "C:\DEV\Python27\lib\subprocess.py", line 672, in init errread, errwrite) File "C:\DEV\Python27\lib\subprocess.py", line 882, in _execute_child startupinfo) WindowsError: [Error 2] Le fichier spécifié est introuvable (file not found in french)
  • Jochen Ritzel
    Jochen Ritzel over 13 years
    @user462794: It seems you ignored the @contextlib.contextmanager line
  • idjaw
    idjaw almost 8 years
    Just wanted to add the note that to make this Python 3 friendly, you have to import StringIO from io => from io import StringIO.
  • WGH
    WGH about 7 years
    You should wrap the last two lines of the generator function in try-finally, though.
  • Hairy
    Hairy over 4 years
    Why yield? As opposed to what sergzach is doing below you?
  • julianhatwell
    julianhatwell about 3 years
    contextlib has a redirect_stdout out of the box. Is there any reason you didn't use this in your solution? Seems to work well.
  • Jochen Ritzel
    Jochen Ritzel about 3 years
    @julianhatwell Well, the answer is 10 years old, it didn't exist back then :-) I've added your comment, thanks.