Every novice Pythonista is familiar with the trusty built-in function print(). Its job is simple: output the string representation of an object or expression.

print('the answer is', 1 + 2)
'the answer is 3'

But let’s say you want to change the behavior of print. Instead of sending its output to the terminal, let’s send that output to a file on the disk. We can accomplish this by redefining the print variable.

Printing to a File

The full function signature of print is:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

Note the third keyword argument, file. The default value of file is sys.stdout, which represents the terminal output buffer. The only real requirement for the object passed as file is that it has a .write() method. You know what has a write method? File handlers.

with open('log.txt', 'a+') as f: # f is a file handler
    f.write('the answer is', 1 + 2)

Let’s redefine the print variable to instead be a wrapper function around the vanilla built-in print(). The wrapped print() needs to be referenced through the builtins module so that we don’t define a recursive function.

import builtins

def print(*args, **kwargs):
    with open('log.txt', 'a+') as f:
        return builtins.print(*args, file=f, **kwargs)

print('the answer is', 1 + 2)

Now, when we call print(), no output is displayed in the terminal. However, if we read the contents of log.txt, we can see that our redefined print() has written to the file.

$ cat log.txt
the answer is 3

Print as a Buffer Object

Let’s make it weird. We can actually redefine print() to be a buffer object that stores all the strings that are passed to it for printing. Instead of a function, print will be an instance of a class that inherits io.StringIO.

StringIO is a in-memory text buffer that works similarly to a file handler. In fact, file handlers and StringIO both implement the abstract base class IOBase, which means they have many of the same methods defined.

Here’s a simple example of how to use StringIO:

import io

buffer = io.StringIO()
buffer.write('here is some input')
buffer.getvalue()
'here is some input'

Let’s define a new subclass of StringIO called PrintWrapper. We’re also going to define a __call__ magic method on this new class. __call__ allows you to define how an instance of a class behaves when it is called like a function.

import builtins
import io

class PrintWrapper(io.StringIO):
  def __call__(self, *args, **kwargs):
    # Pass the object instance (self) as the file
    return builtins.print(*args, file=self, **kwargs)

print = PrintWrapper() # print is now the printer AND a buffer
print('this was added to buffer')  # triggers print.__call__()
print('this was also added to the buffer')
print.getvalue()
'this was added to buffer\nthis was also added to buffer\n'

Strings that you have printed are seperated by \n when you call print.getvalue(), but you can define a different seperator by passing it as a string to the end= keyword argument of builtins.print().

Conclusion

Why would you ever want to do any of this? Let’s say you are building a web-based programming environment like CoderPad. When a user calls print(), you wouldn’t want the Python interpreter running on the server to send the printed statement to stdout. You want it outputted to a handler that transmits it to the user’s browser over the Internet. There are probably other ways to accomplish this, but redefining print() behind the scenes is one possibility.

Edit: As Reddit user Dasher38 points out, there’s a context manager in the standard library contextlib.redirect_stdout that is better suited for redirecting the output of a web-based programming environment backend. redirect_stdout redirects everything sent to sys.stdout (including printed statements) to a specified file-like object.



This is the first of a series of blog entries I’m calling “baby snakes”, bits of Python arcana aimed at intermediate and advanced Python developers. Check back soon for more!