Module reload and wxPython

Hung Jung Lu hungjunglu at yahoo.com
Sat May 3 17:41:33 EDT 2003


Hi,

Python is cool in that it has a powerful module reloading scheme.

I have written a cute little module reloader using wxPython. If you
have wxPython installed, you can download the following two files and
try it out. Don't just read the code, you have to run it to feel it
and to understand it.

(1) The module reloader itself was developed using itself. That is,
bootstrapping. It's really cool. I did not need to restart the program
while developing it, except for very few times. Theoretically it is
possible in Python to write an entire wxPython application without
ever shutting down the application itself. Developing UI (User
Interface) this way is as comfortable as, or more comfortable than,
using other GUI development systems. This way of programming is what
many other programming languages cannot even dream of.

(2) Upon reloading, the existing instances of classes can relink to
the new classes. The trick is in (a) calling the "_dash()" method in
the constructor to register an object instance, (b) the "reloader
coda" code snippet added at the end of a module. (The name "_dash"
does not  mean anything, I chose it just because it's easy to type!)

(3) The above trick is good for development. In actual production, it
will lead to memory leak if many transient instances are created and
discarded elsewhere. So, there is a sys.__debug__ flag: when absent,
the trick is suppressed.

(4) Classes can also have the optional __reload__() method to execute
statements during upon new reload.

Try it out: you can modify the ModuleReloaderFrame object almost at
will. (E.g.: modifying the position of the buttons, changing text,
color, adding other wxPython widgets, etc.) Of course, I have written
it in such a way that it can be used with other wxPython applications.
Just remember to insert the _dash() calls in the class constructors,
and append the "reloader coda" at the end of the modules. It's not too
much extra work, and the payoff is great. Of course, I don't think the
trick works for more esotheric modules/classes. But for "normal"
modules and classes, it should work.

Question: what I would like to know is, is there a simpler way of
inserting the "reloader coda" snippet into modules?
 I am thinking along the line of __import__ or ihooks. (All this leads
back to the discussion of Aspect-Oriented Programming. I can really
see that hooks are very important in modern programming, but that most
programming languages don't handle hooks very well.) I guess it's
possible to write something that grabs existing modules and
automatically hook the "_dash()" to class constructors and also hook
the "reloader coda" to modules. But the result could be a bit
confusing/surprising to the module programmers.

Have fun and comments are welcome. I am sure a lot of experts have
done something along this line, and I'd like to see alternative
approaches.

Hung Jung

#-----------------------------------------------
# TestWxUtils.py
#
#   This is an example of using
#   wxUtils.ModuleReloaderFrame. 
#-----------------------------------------------
import sys
sys.__debug__ = sys # comment out this line to suppress instance-class
re-linking

from wxPython.wx import *
import wxUtils
class App(wxApp):
    def __init__(self):
        wxApp.__init__(self, True)
    def OnInit(self):
        w = wxUtils.ModuleReloaderFrame(None)
        self.SetTopWindow(w)
        return True

app = App()
app.MainLoop()

#--------------------------------------------------
# wxUtils.py
#
#   This module contains a wxPython implementaion
#   of a module reloader application window.
#--------------------------------------------------

import sys
from wxPython.wx import *

class ModuleReloaderFrame(wxFrame):

    def __init__(self, parent):
        _dash(self)
        wxFrame.__init__(self, parent, wxNewId(), 'Module Reloader',
size=(350, 240),
                         style=wxDEFAULT_FRAME_STYLE ^
(wxRESIZE_BORDER | wxMAXIMIZE_BOX))
        EVT_CLOSE(self, self.OnFrameClose)
        self.build_panel()
        self.__reload__()
        if parent is None: self.Show(True)
        
    def __reload__(self):
        self.build_listbox()
        self.build_reload_button()
        self.build_add_module_button()
        self.build_about_button()

    def build_panel(self):
        self.panel = wxPanel(self, -1)
        self.panel.SetBackgroundColour(wxWHITE)
        
    def build_listbox(self):
        try: self.modules
        except: self.modules = {__name__: sys.modules[__name__]}
        if hasattr(self, 'listbox'):
            listbox_selection = self.listbox.GetSelection()
            self.listbox.Destroy()
        else:
            listbox_selection = 0
        # create new list box
        id = wxNewId()
        self.listbox = wxListBox(self.panel, id,
                                 wxPoint(120,13),
                                 wxSize(200, 180),
                                 [], wxLB_SINGLE)
        module_names = self.modules.keys()
        module_names.sort()
        for m in module_names:
            self.listbox.Append(m, self.modules[m])
        EVT_LISTBOX_DCLICK(self, id, self.OnListBoxDClick)
        self.listbox.SetSelection(listbox_selection)
    
    def build_reload_button(self):
        if hasattr(self, 'reload_button'):
self.reload_button.Destroy()
        id = wxNewId()
        self.reload_button = wxButton(self.panel, id, 'Reload',
wxPoint(20, 50))
        EVT_BUTTON(self, id, self.OnReloadButton)
        self.reload_button.SetDefault()

    def build_add_module_button(self):
        if hasattr(self, 'add_module_button'):
self.add_module_button.Destroy()
        id = wxNewId()
        self.add_module_button = wxButton(self.panel, id, 'Add
Module', wxPoint(20, 90))
        EVT_BUTTON(self, id, self.OnAddModuleButton)

    def build_about_button(self):
        if hasattr(self, 'about_button'): self.about_button.Destroy()
        id = wxNewId()
        self.about_button = wxButton(self.panel, id, 'About',
wxPoint(20, 130))
        EVT_BUTTON(self, id, self.OnAboutButton)

    def OnListBoxDClick(self, event):
        self.reload_selected_module()

    def OnReloadButton(self, event):
        self.reload_selected_module()

    def OnAddModuleButton(self, event):
        dlg = wxTextEntryDialog(self, 'Enter the module\'s name', 'Add
Module', '')
        module_name = ''
        if dlg.ShowModal() == wxID_OK:
            module_name = dlg.GetValue().strip()
        dlg.Destroy()
        if module_name != '':
            try:
                exec 'import %s' % module_name
            except:
                dlg = wxMessageDialog(self,
                      'Failed to add module <%s>.\nPlease check the
module name.' % module_name,
                      'ModuleReloader Error', wxOK | wxICON_ERROR)
                dlg.ShowModal()
                dlg.Destroy()
                return
            module = sys.modules[module_name]
            self.modules[module_name] = module
            self.refresh_listbox(module)

    def OnAboutButton(self, event):
        dlg = wxMessageDialog(self, 'Module Reloader\n\nHung Jung Lu,
2003',
                              'Credit', style=wxOK |
wxICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()
                
    def refresh_listbox(self, selected_module=None):
        self.listbox.Clear()
        module_names = self.modules.keys()
        module_names.sort()
        i_selection = 0
        for m in module_names:
            module = self.modules[m]
            self.listbox.Append(m, module)
            if module is selected_module:
                self.listbox.SetSelection(i_selection)
            i_selection += 1
            
    def reload_selected_module(self):
        module = self.listbox.GetClientData(self.listbox.GetSelection())
        try:
            reload(module)
        except:
            dlg = wxMessageDialog(self,
                  'Failed to reload module <%s>.\nPlease check the
stderr window.' % module.__name__,
                  'ModuleReloader Error', wxOK | wxICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()
            raise
        dlg = wxMessageDialog(self,
              'Done!\n<%s> reloaded.' % module.__name__,
              'Module Reloader', wxOK | wxICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def OnFrameClose(self, event):
        if event.CanVeto():
            if self.GetParent() is None: # top window
                self.Destroy()
            else: # not top window
                self.Show(False)
                event.Veto()
        else:
            self.Destroy()
        
#-----------------
# reloader coda
#-----------------
import sys
try: sys.__debug__, _dashed_objects
except: _dashed_objects = []
try:
    sys.__debug__
    def _dash(obj): _dashed_objects.append(obj)
except:
    def _dash(obj): pass
for obj in _dashed_objects:
    try: obj.__class__ = eval(obj.__class__.__name__)
    except: pass
    if hasattr(obj, '__reload__'): obj.__reload__()




More information about the Python-list mailing list