3

I'm working on a code-checker for my students (I tutor). The project is that they write a function that prints a specific string using the print keyword. I want to be able to test what they have printed by storing it and matching to a list (or something similar). The basic setup is:

def checker():
    #run user code
    do some other things like save and check error messages etc

Now somewhere in this checker function I want to be able to keep track of what was printed. In Javascript, I was able to do something like:

var logs = [];
var hold_logger = console.log //saves the console.log so nothing gets ruined
console.log = function (x) { logs.push(x) };

Now when I run the students code, instead of printing to the console, it pushes the value to logs. I want to achieve the same thing in Python 2.7.

marisbest2
  • 1,346
  • 2
  • 17
  • 30
  • You could just run it with subprocess, and you'll get a file handle on the stdout. – Lennart Regebro Aug 08 '13 at 21:27
  • Out of curiosity, why are you teaching your students Python 2.7 instead of 3.3? – abarnert Aug 08 '13 at 21:32
  • @abarnert So many bad reasons, chief among them that I don't set the curriculum. – marisbest2 Aug 08 '13 at 21:35
  • @marisbest2: Understood. Well, if you ever do get a chance to teach 3.x, you can replace the `print` function exactly the same way you'd replace the `console.log` function in JS. It still might not be the right answer, but it's nice to have the option. – abarnert Aug 08 '13 at 21:59

3 Answers3

7

You can assign a different file-like object to sys.stdout; anything that is printed is written to that object.

You can use a io.BytesIO() object to replace stdout:

import sys
from io import BytesIO

orig_stdout, sys.stdout = BytesIO()

io is the newer, more robust I/O library from Python 3, available in Python 2 as well; io.BytesIO() is the more robust version of StringIO.StringIO().

You can then inspect what was printed by calling sys.stdout.getvalue(); when done, you can restore from orig_stdout.

Demo:

>>> import sys
>>> from io import BytesIO
>>> orig_stdout, sys.stdout = sys.stdout, BytesIO()
>>> print 'Hello world!'
>>> output = sys.stdout.getvalue()
>>> sys.stdout = orig_stdout
>>> output
'Hello world!\n'

Note that I restored sys.stdout from an earlier reference. You could also use sys.__stdout__, provided nothing else replaces sys.stdout; saving a reference to what sys.stdout is pointing to now is safer.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • For Python3 `StringIO` should be imported like this: `from io import StringIO` – niekas Mar 14 '19 at 05:28
  • @niekas in Python 3 you can just replace the `print()` function. – Martijn Pieters Mar 14 '19 at 08:09
  • In my case it is more convenient to replace whole `sys.stdout` in order to capture the final text printed out. I have multiple `print(, end='')` statements and I check the the final outcome in my test. – niekas Mar 14 '19 at 12:19
  • 1
    @niekas: a custom `print()` function can still append to a shared file object. :-) Just to be clear, this question is explicitly tagged with `python-2.7`. See [this other answer of mine](https://stackoverflow.com/questions/34871605/stringio-portability-between-python2-and-python3-when-capturing-stdout/34872005#34872005) for a version compatible across Python 2 and 3. – Martijn Pieters Mar 14 '19 at 13:15
1

Here's one way - you can replace the standard output file object with a custom one:

import sys

# A very basic logging class (it should really implement all the file object methods, but for your purposes, this will probably suffice.
class basicLogger:
    def __init__(self):
        self.log = []
    def write(self, msg):
        self.log.append(msg)
    def writelines(self, msgs):
        for msg in msgs:
            self.log.append(msg)

log = basicLogger()

# Replaces the default output file object with your logger
sys.stdout = log

def checker():
    #run user code
    print "hello!"
    print
    print "I am a student!"

checker()

# This replaces the original output file object.
sys.stdout = sys.__stdout__

print "Print log:"
for msg in log.log:
    print "\t", msg,

output:

Print log:
    hello!  

    I am a student! 
Brionius
  • 13,858
  • 3
  • 38
  • 49
0

The Python equivalent of what you're doing in Java is to replace sys.stdout with a custom file-like object.

Of course this won't just catch what they print; it'll also catch what they sys.stdout.write, or, depending on their settings, maybe things they logging.log or the output of subprocesses they run, etc. But there's really no way around that, because print isn't a function you can replace. (Of course if you taught them 3.x instead, that would be a different story.)


However, it's probably simpler to not do this at all. Just capture the stdout from outside of Python. For example:

for student in student??.py:
    python ${student} > ${student}.out

Or, if you insist, do the same thing with subprocess from within Python:

output = {student: subprocess.check_output('{}.py'.format(student)) 
          for student in students}

(You'd probably want to wrap that in something that caught and stashed exceptions, but that should be enough to give you the idea.)

abarnert
  • 354,177
  • 51
  • 601
  • 671