wxPython – Redirecting stdout / stderr

Posted by Mike on January 1st, 2009 filed in Cross-Platform, Python, wxPython

New programmers start using Python and wxPython each week. So it follows that every few months, I see people asking how to redirect stdout to a wx.TextCtrl on comp.lang.python or the wxPython mailing list. Since this is such a common question, I thought I would write an article about it. Regular readers will know that I have mentioned this concept before in a previous post.

First off, we’ll need to create a class that can duck-type the writing API of a wx.TextCtrl. Notice that I use the so-called “new style” class that subclasses object (see code below).

class RedirectText(object):
    def __init__(self,aWxTextCtrl):
        self.out=aWxTextCtrl
 
    def write(self,string):
        self.out.WriteText(string)

Note that there’s only one method in this class. It allows us to write the text from stdout or stderr to the text control. It should be noted that the write method is not thread-safe. If you want to redirect text from threads, then change the write statement like this:

def write(self, string):
    wx.CallAfter(self.out.WriteText, string)

Now let’s dig into the wxPython code we’ll need:

import sys
import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "wxPython Redirect Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
                          style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        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
        redir=RedirectText(log)
        sys.stdout=redir
 
    def onButton(self, event):        
        print "You pressed the button!"
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

In the code above, I create a read-only multi-line text control and a button whose sole purpose is to print some text to stdout. I add them to a BoxSizer to keep the widgets from stacking on top of each other and to better handle resizing of the frame. Next I instantiate the RedirectText class by passing it an instance of my text control. Finally, I set stdout to the RediectText instance, redir (i.e. sys.stdout=redir).

If you want to redirect stderr as well, then just add the following right after “sys.stdout=redir”: sys.stderr=redir

And that’s all there really is to it. Improvements could be made to this, namely color coding (or pre-pending) which messages are from stdout and which are from stderr, but I’ll leave that as an exercise for the reader.

Download the Source

Print Friendly

  • Adam Fraser

    logging -> wx.TextCtrl

    I was looking all over for a similar solution that actually redirects logging to a textctrl and managed to find one person with a partial solution (http://www.velocityreviews.com/forums/t515815-wxpython-redirect-the-stdout-to-a-textctrl.html). Here’s my working modification for anyone else out there who may be looking:
    [code]
    import wx
    import logging
    import sys

    class WxLog(logging.Handler):
    def __init__(self, ctrl):
    logging.Handler.__init__(self)
    self.ctrl = ctrl
    def emit(self, record):
    self.ctrl.AppendText(self.format(record)+”n”)

    class MainFrame(wx.Frame):
    def __init__(self):
    wx.Frame.__init__(self, None, title=”logging test”)
    self.level = 1
    log = wx.TextCtrl(self, style=wx.TE_MULTILINE)
    criticalbtn = wx.Button(self, -1 , ‘critical’)
    errorbtn = wx.Button(self, -1 , ‘error’)
    warnbtn = wx.Button(self, -1 , ‘warn’)
    infobtn = wx.Button(self, -1 , ‘info’)
    debugbtn = wx.Button(self, -1 , ‘debug’)
    togglebtn = wx.Button(self, -1 , ‘toggle level’)
    sizer = wx.BoxSizer(wx.VERTICAL)
    sizer.Add(log, 1, wx.EXPAND)
    sizer.Add(criticalbtn)
    sizer.Add(errorbtn)
    sizer.Add(warnbtn)
    sizer.Add(infobtn)
    sizer.Add(debugbtn)
    sizer.Add(togglebtn)
    self.SetSizer(sizer)

    self.logr = logging.getLogger(”)
    self.logr.setLevel(logging.DEBUG)
    hdlr = WxLog(log)
    hdlr.setFormatter(logging.Formatter(‘%(levelname)s | %(name)s |%(message)s [@ %(asctime)s in %(filename)s:%(lineno)d]’))
    self.logr.addHandler(hdlr)
    self.logr.debug(str(sys.stdout))

    self.Bind(wx.EVT_BUTTON, self.on_toggle, togglebtn)
    self.Bind(wx.EVT_BUTTON, self.on_critical, criticalbtn)
    self.Bind(wx.EVT_BUTTON, self.on_error, errorbtn)
    self.Bind(wx.EVT_BUTTON, self.on_warn, warnbtn)
    self.Bind(wx.EVT_BUTTON, self.on_info, infobtn)
    self.Bind(wx.EVT_BUTTON, self.on_debug, debugbtn)

    def on_toggle(self, evt):
    self.level = (self.level+1) % 5
    levels = [logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG]
    self.logr.setLevel(levels[self.level])
    print self.level, levels[self.level]

    def on_critical(self, evt):
    self.logr.critical(‘Test message.’)

    def on_error(self, evt):
    self.logr.error(‘Test message.’)

    def on_warn(self, evt):
    self.logr.warn(‘Test message.’)

    def on_info(self, evt):
    self.logr.info(‘Test message.’)

    def on_debug(self, evt):
    self.logr.debug(‘Test message.’)

    if __name__ ==”__main__”:
    app = wx.App(0)
    frame = MainFrame()
    frame.Show()
    app.MainLoop()
    [/code]

  • Adam Fraser

    logging -> wx.TextCtrl

    I was looking all over for a similar solution that actually redirects logging to a textctrl and managed to find one person with a partial solution (http://www.velocityreviews.com/forums/t515815-wxpython-redirect-the-stdout-to-a-textctrl.html). Here’s my working modification for anyone else out there who may be looking:
    [code]
    import wx
    import logging
    import sys

    class WxLog(logging.Handler):
    def __init__(self, ctrl):
    logging.Handler.__init__(self)
    self.ctrl = ctrl
    def emit(self, record):
    self.ctrl.AppendText(self.format(record)+”\n”)

    class MainFrame(wx.Frame):
    def __init__(self):
    wx.Frame.__init__(self, None, title=”logging test”)
    self.level = 1
    log = wx.TextCtrl(self, style=wx.TE_MULTILINE)
    criticalbtn = wx.Button(self, -1 , ‘critical’)
    errorbtn = wx.Button(self, -1 , ‘error’)
    warnbtn = wx.Button(self, -1 , ‘warn’)
    infobtn = wx.Button(self, -1 , ‘info’)
    debugbtn = wx.Button(self, -1 , ‘debug’)
    togglebtn = wx.Button(self, -1 , ‘toggle level’)
    sizer = wx.BoxSizer(wx.VERTICAL)
    sizer.Add(log, 1, wx.EXPAND)
    sizer.Add(criticalbtn)
    sizer.Add(errorbtn)
    sizer.Add(warnbtn)
    sizer.Add(infobtn)
    sizer.Add(debugbtn)
    sizer.Add(togglebtn)
    self.SetSizer(sizer)

    self.logr = logging.getLogger(”)
    self.logr.setLevel(logging.DEBUG)
    hdlr = WxLog(log)
    hdlr.setFormatter(logging.Formatter(‘%(levelname)s | %(name)s |%(message)s [@ %(asctime)s in %(filename)s:%(lineno)d]’))
    self.logr.addHandler(hdlr)
    self.logr.debug(str(sys.stdout))

    self.Bind(wx.EVT_BUTTON, self.on_toggle, togglebtn)
    self.Bind(wx.EVT_BUTTON, self.on_critical, criticalbtn)
    self.Bind(wx.EVT_BUTTON, self.on_error, errorbtn)
    self.Bind(wx.EVT_BUTTON, self.on_warn, warnbtn)
    self.Bind(wx.EVT_BUTTON, self.on_info, infobtn)
    self.Bind(wx.EVT_BUTTON, self.on_debug, debugbtn)

    def on_toggle(self, evt):
    self.level = (self.level+1) % 5
    levels = [logging.CRITICAL, logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG]
    self.logr.setLevel(levels[self.level])
    print self.level, levels[self.level]

    def on_critical(self, evt):
    self.logr.critical(‘Test message.’)

    def on_error(self, evt):
    self.logr.error(‘Test message.’)

    def on_warn(self, evt):
    self.logr.warn(‘Test message.’)

    def on_info(self, evt):
    self.logr.info(‘Test message.’)

    def on_debug(self, evt):
    self.logr.debug(‘Test message.’)

    if __name__ ==”__main__”:
    app = wx.App(0)
    frame = MainFrame()
    frame.Show()
    app.MainLoop()
    [/code]

  • mgag

    Figured it out – that didn’t take long. I am posting from threads, so I needed the thread-safe version of the “emit”, like so,

    class WxLog(logging.Handler):
    def __init__(self, ctrl):
    logging.Handler.__init__(self)
    self.ctrl = ctrl

    def emit(self, record):
    log = self.format(record)+”n”
    wx.CallAfter(self.ctrl.AppendText, log)

  • mgag

    Figured it out – that didn’t take long. I am posting from threads, so I needed the thread-safe version of the “emit”, like so,

    class WxLog(logging.Handler):
    def __init__(self, ctrl):
    logging.Handler.__init__(self)
    self.ctrl = ctrl

    def emit(self, record):
    log = self.format(record)+”\n”
    wx.CallAfter(self.ctrl.AppendText, log)

  • mgag

    I was posting from multiple threads, so I needed the fix from the original submission,

    class WxLog(logging.Handler):
    def __init__(self, ctrl):
    logging.Handler.__init__(self)
    self.ctrl = ctrl

    def emit(self, record):
    log = self.format(record)+”n”
    wx.CallAfter(self.ctrl.AppendText, log)

  • mgag

    I was posting from multiple threads, so I needed the fix from the original submission,

    class WxLog(logging.Handler):
    def __init__(self, ctrl):
    logging.Handler.__init__(self)
    self.ctrl = ctrl

    def emit(self, record):
    log = self.format(record)+”\n”
    wx.CallAfter(self.ctrl.AppendText, log)

  • http://www.pythonlibrary.org Mike

    Thanks guys. Sorry my commenting system didn’t keep the formatting of your code very well. I think someone mentioned that I should try writing a redirect logging tutorial one time, but now I don’t have to.

    – Mike

  • Anonymous

    Just save a reference to stdout. Something like “self.stdoutRef = sys.stdout”. Then after you’ve redirected it, you can restore it by doing the reverse: sys.stdout = self.stdoutRef

  • Anonymous

    What do you mean? It won’t be able to redirect anything until the MainLoop starts. If you have loops, put them in a function and call that after your application starts. You can use a button or just wx.CallAfter.

    – Mike