Debugging wxPython programs

wxPython is a great way to write full-featured native GUI applications in Python.  It’s a wrapper for wxWidgets, a portable windowing toolkit written in C++.  (wxWidgets used to be called wxWindows, but, thanks to Microsoft’s inexcusable penchant for giving all their products idiotically generic names, inevitably had to be renamed.)

But it can be painstaking to add program features: you add a few lines, restart the app, and try to test that they behave correctly.  Then repeat for the next few lines. This is only made a harder by Python’s dynamic typing, because so many more incorrect pieces of code are happily admitted by the parser and runtime, and only become apparent when actually executed.

With most Python programs you can use the command line interpreter to run individual functions, or type in snippets of Python code to quickly test them.  You can also use Python’s introspective facilities to find out how to use a module.  There are two particular functions which are great for this:

  1. dir(x), which will return a list of the properties on xx could be a module, class or any other object; also the somewhat longwinded [k for k in dir(x) if 'str' in k] when the list is long and you want to cut it down to likely possibilities.
  2. help(x), which will print all available inbuilt documentation on the object x.  By default the help text will consist of the a list of the method definitions in x (if x is a class or an instance of a class), or a recursive list of definitions in a whole module if x is a module.  It will also include the docstrings if any are present.

When I’m learning to use a new module, I will load it on the command line, and find what I need using the these two functions.

But running a wxPython program is all or nothing affair.  The wxPython app is initialised, and then control is passed to the inbuilt main program loop.  How many times I’ve wanted to examine the value of part of my running app, just to see how it’s behaving!  The workaround has been to add lots of print statements.

It occurred to me today that I could possibly run it alongside the Python command line.

Typical wxPython programs, as I write them, have a file main.py that looks like this:

from gui import MainFrame
import wx

def main():
    app = wx.PySimpleApp()
    frame = MainFrame()

    frame.Show(1)
    app.MainLoop()

if __name__ == '__main__':
    main()

app and frame are Python objects that wrap a lot of serious C++ GUI code, which interacts with the operating system.  Essentially all of the application state is contained in those objects. Normally when the program is run, it is executed as above. When app.MainLoop is called, wxPython will take over and display an interactive, native window. That function only returns when the main window is closed.

I want to keep the command line running in the main thread, and launch the GUI in another thread. Doing so would mean I can test the GUI normally, but can also run snippets of code and introspect the running GUI from the command line.

I tried creating app and frame directly on the command line, and then starting app.MainLoop as a new thread:

from gui import MainFrame
import wx
app = wx.PySimpleApp()
frame = MainFrame()
frame.Show(1)
import threading
t = threading.Thread(target=app.MainLoop)
t.start()

It does not work, because wxPython has certain expectations about how these vital functions are executed. In particular, the main GUI code, including initialising the app and creating the main window, should all run from a single thread.

So instead, I tried something even simpler:

# In main.py:
def main():
    global app, frame
    ...

# On the command line:
import main
import threading
t = threading.Thread(target=main.main)
t.start()

At this point the GUI opens and runs normally. And the command line is ready for input at the same time! I can inspect it:

>>> main.frame.GetTitle()
'SQL Editor'

>>> tab = main.frame.notebook.GetCurrentPage()
>>> dir(tab.tc) # Find out what methods the text control on the tab supports
[..., 'Clear', ...]
>>> tab.tc.Clear() # Remove all the text in the GUI text box on that tab
>>> main.frame.Refresh() # Tell the GUI to update itself

These can be used to manipulate the running program in ad-hoc ways. There are dangers in doing so; a wild thread is coming and poking round in the internals of a running program, after all. But the GUI is mostly idle waiting for an event; in practice it is generally safe to examine it. Python’s much-maligned Global Interpreter Lock protects the simple Python objects in it, and the C++ parts of wxWidgets are threadsafe — up to a limit. wxWidgets uses a single thread for all graphical updates, which is why Refresh must be used from another thread to trigger drawing activity, rather than using drawing methods directly.

Advertisements
This entry was posted in Programming and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s