Wednesday, November 28, 2018

Python 101: Redirecting stdout

Source: https://www.blog.pythonlibrary.org
Redirecting stdout to something most developers will need to do at some point or other. It can be useful to redirect stdout to a file or to a file-like object. I have also redirected stdout to a text control in some of my desktop GUI projects. In this article we will look at the following:
  • Redirecting stdout to a file (simple)
  • The Shell redirection method
  • Redirecting stdout using a custom context manager
  • Python 3’s contextlib.redirect_stdout()
  • Redirect stdout to a wxPython text control


Redirecting stdout

The easiest way to redirect stdout in Python is to just assign it an open file object. Let’s take a look at a simple example:
import sys
 
def redirect_to_file(text):
    original = sys.stdout
    sys.stdout = open('/path/to/redirect.txt', 'w')
    print('This is your redirected text:')
    print(text)
    sys.stdout = original
 
    print('This string goes to stdout, NOT the file!')
 
if __name__ == '__main__':Redirecting stdout / stderr
    redirect_to_file('Python rocks!')
Here we just import Python’s sys module and create a function that we can pass strings that we want to have redirected to a file. We save off a reference to sys.stdout so we can restore it at the end of the function. This can be useful if you intend to use stdout for other things. Before you run this code, be sure to update the path to something that will work on your system. When you run it, you should see the following in your file:

This is your redirected text:
Python rocks!

That last print statement will go to stdout, not the file.

Shell Redirection

Shell redirection is also pretty common, especially in Linux, although Windows also works the same way in most cases. Let’s create a silly example of a noisy function that we will call noisy.py:
# noisy.py
def noisy(text):
    print('The noisy function prints a lot')
    print('Here is the string you passed in:')
    print('*' * 40)
    print(text)
    print('*' * 40)
    print('Thank you for calling me!')
 
if __name__ == '__main__':
    noisy('This is a test of Python!')
You will notice that we didn’t import the sys module this time around. The reason is that we don’t need it since we will be using shell redirection. To do shell redirection, open a terminal (or command prompt) and navigate to the folder where you saved the code above. Then execute the following command:

python noisy.py > redirected.txt

The greater than character (i.e. >) tells your operating system to redirect stdout to the filename you specified. At this point you should have a file named “redirected.txt” in the same folder as your Python script. If you open it up, the file should have the following contents:

The noisy function prints a lot
Here is the string you passed in:
****************************************
This is a test of Python!
****************************************
Thank you for calling me!

Now wasn’t that pretty cool?

Redirect stdout with a context manager

Another fun way to redirect stdout is by using a context manager. Let’s create a custom context manager that accepts a file object to redirect stdout to:
import sys
from contextlib import contextmanager
 
 
@contextmanager
def custom_redirection(fileobj):
    old = sys.stdout
    sys.stdout = fileobj
    try:
        yield fileobj
    finally:
        sys.stdout = old
 
if __name__ == '__main__':
    with open('/path/to/custom_redir.txt', 'w') as out:
        with custom_redirection(out):
            print('This text is redirected to file')
            print('So is this string')
        print('This text is printed to stdout')
When you run this code, it will write out two lines of text to your file and one to stdout. As usual, we reset stdout at the end of the function.

Using contextlib.redirect_stdout

Python 3.4 added the redirect_stdout function to their contextlib module. Let’s try using that to create a context manager to redirect stdout:
import sys
from contextlib import redirect_stdout
 
def redirected(text, path):
    with open(path, 'w') as out:
        with redirect_stdout(out):
            print('Here is the string you passed in:')
            print('*' * 40)
            print(text)
            print('*' * 40)
 
if __name__ == '__main__':
    path = '/path/to/red.txt'
    text = 'My test to redirect'
    redirected(text, path)
This code is a little simpler because the built-in function does all the yielding and resetting of stdout automatically for you. Otherwise, it works in pretty much the same way as our custom context manager.

Redirecting stdout in wxPython

wxredirect

import sys
import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None,
                          title="wxPython Redirect Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL
        log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
                          style=style)
        btn = wx.Button(panel, wx.ID_ANY, 'Push me!')
        self.Bind(wx.EVT_BUTTON, self.onButton, btn)
 
        # Add widgets to a sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)
 
        # redirect text here
        sys.stdout = log
 
    def onButton(self, event):
        print "You pressed the button!"
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm().Show()
    app.MainLoop()

This code just creates a simple frame with a panel that contains a multi-line text control and a button. Whenever you press the button, it will print out some text to stdout, which we have redirected to the text control.

Personally I thought it was cool that Python 3 now has a context manager built-in just for this purpose. Speaking of which, Python 3 also has a function for redirecting stderr. All of these examples can be modified slightly to support redirecting stderr or both stdout and stderr. The very last thing we touched on was redirecting stdout to a text control in wxPython. This can be really useful for debugging or for grabbing the output from a subprocess, although in the latter case you will need to print out the output to have it redirected correctly.

Related Reading