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:
dir(x), which will return a list of the properties on
xcould 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.
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
xis a class or an instance of a class), or a recursive list of definitions in a whole module if
xis 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()
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
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.