wx.lib.pubsub and wx.EVT_CLOSE

wx.lib.pubsub is making my day… keeping my custom events nice and straight so that I only have to use regular wx.EVTs where absolutely necessary.

However, I notice that I get PyDeadObjectErrors in the following conditions: I subscribe a window to some topic, and then somewhere along the way the same window is closed by the user when they’re finished with it, then at some point the trigger event happens and the topic gets broadcast. Because wxwindows don’t actually get destroyed when they’re closed, only hidden, the Python part of the window is still alive to receive the message it subscribed to. But if the action it takes in its message handler references any native C++ part that it contained – such as altering a text control or even in some cases referencing variables, a PyDeadObjectError occurs.

So to deal with this, I’ve been having to manually bind any window that subscribes to almost anything to wx.EVT_CLOSE, and have the handler for the close event call Publisher().unsubscribe to unregister the previous subscription (then of course Skip() the close event so things run properly).

Would it not be more appropriate, and expected, for the pubsub Publisher() to take care of this? Thinking this through, I guess if you’re keeping a window openable and closable by just showing and unshowing it when necessary, rather than destroying and recreating it, then you really wouldn’t want it to lose its subscriptions during a time when it was hidden. What if it didn’t update its contents properly (etc.) as a result…

So would it then be more appropriate, in situations when you wish the closing of a window to be equivalent to its full destruction, to bind the close event to a method that calls self.Destroy()? I guess that depends on your specific purposes and on just how much computation is going into creating vs. destroying your windows vs. keeping them subscribed to (and handling) whatever quantity of things they may be hooked up to.

Due to the pubsub using weakrefs, if you are using
wx.lib.pubsub.Publisher.subscribe(topic, object.callback_method), then
as long as the rest of your program isn't keeping a reference to the
widget that you destroyed, pubsub should be dropping its reference to
the callback.

As an alternative, as long as you aren't doing dynamic method rebindings
(obj.method = newmethod), you can use a mechanism that can abstracts all
that away and you won't see any more PyDeadObjectErrors anymore...

class method_subscribe:
    def __getattr__(self, methodname):
        def callback_(object, topic):
            #sanity check
            getattr(object, methodname)
            def _callback(message):
                try:
                    meth = getattr(object, methodname)
                except wx.PyDeadObjectError:
                    wx.lib.pubsub.Publisher.unsubscribe(_callback, topic)
                else:
                    meth(message)
            wx.lib.pubsub.Publisher.subscribe(_callback, topic)
        return callback_

subscribe = method_subscribe()

Use it like...

    def __init__(self, ...):
        ...
        subscribe.method(self, topic)
        ...

Generally:
    subscribe.method(self, topic)
is equivalent to:
    Publisher.subscribe(self.method, topic)
with some extra magic thrown in for good measure. :slight_smile:

- Josiah

···

"Eric Ongerth" <ericongerth@gmail.com> wrote:

wx.lib.pubsub is making my day... keeping my custom events nice and straight
so that I only have to use regular wx.EVTs where absolutely necessary.

However, I notice that I get PyDeadObjectErrors in the following conditions:
I subscribe a window to some topic, and then somewhere along the way the
same window is closed by the user when they're finished with it, then at
some point the trigger event happens and the topic gets broadcast. Because
wxwindows don't actually get destroyed when they're closed, only hidden, the
Python part of the window is still alive to receive the message it
subscribed to. But if the action it takes in its message handler references
any native C++ part that it contained -- such as altering a text control or
even in some cases referencing variables, a PyDeadObjectError occurs.

I shouldn't post when I'm so tired that I forget to proofread.

- Josiah

···

Josiah Carlson <jcarlson@uci.edu> wrote:

As an alternative, as long as you aren't doing dynamic method rebindings
(obj.method = newmethod), you can use a mechanism that can abstracts all
that away and you won't see any more PyDeadObjectErrors anymore...

Thanks, Josiah. I guess the root of the problem, then, is that some parts of my program must be keeping references to widgets that have been destroyed. Naively I was counting on Python’s reference counting and garbage collection to handle that, but now it makes sense to me that Python can’t be expected to succeed perfectly in that task, given the partial disconnect of wxPython wrapping C++ wxWidgets. Hmm! Sounds like the try/catch approach really is my best bet after all, at least in the immediate future. Thank you for the suggested method. Can you suggest where I might read up on more involved solutions?

···

On 6/3/07, Josiah Carlson jcarlson@uci.edu wrote:

Due to the pubsub using weakrefs, if you are using
wx.lib.pubsub.Publisher.subscribe(topic, object.callback_method), then
as long as the rest of your program isn’t keeping a reference to the
widget that you destroyed, pubsub should be dropping its reference to

the callback.

I don't know where you could look for more solutions. At least with the
obj.method-style calls, you can use a variant of what I provided in my
last message. It should automatically remove method references when
they come to a PyDeadObjectError, and otherwise work the same as it
did before.

As for references that you may be keeping, remember...

class myFrame(wx.Frame):
    def __init__(self, ...):
        ...
        self.panel = wx.Panel(self, ...)
        ...

    def do_something(self, ...):
        ...
        self.panel.Destroy()
        ...

if you run myFrame.do_something(), and you don't later do 'self.panel =
something', then self.panel keeps a reference to the destroyed panel.
Without seeing your code (where you create references, where you destroy
references, etc.), it would be very difficult for us to help you with
fixing such issues.

- Josiah

···

"Eric Ongerth" <ericongerth@gmail.com> wrote:

On 6/3/07, Josiah Carlson <jcarlson@uci.edu> wrote:
>
> Due to the pubsub using weakrefs, if you are using
> wx.lib.pubsub.Publisher.subscribe(topic, object.callback_method), then
> as long as the rest of your program isn't keeping a reference to the
> widget that you destroyed, pubsub should be dropping its reference to
> the callback.

Thanks, Josiah. I guess the root of the problem, then, is that some parts
of my program must be keeping references to widgets that have been
destroyed. Naively I was counting on Python's reference counting and
garbage collection to handle that, but now it makes sense to me that Python
can't be expected to succeed perfectly in that task, given the partial
disconnect of wxPython wrapping C++ wxWidgets. Hmm! Sounds like the
try/catch approach really is my best bet after all, at least in the
immediate future. Thank you for the suggested method. Can you suggest
where I might read up on more involved solutions?

I don’t know where you could look for more solutions. At least with the
obj.method-style
calls, you can use a variant of what I provided in my
last message. It should automatically remove method references when
they come to a PyDeadObjectError, and otherwise work the same as it
did before.

if you run myFrame.do_something(), and you don’t later do ‘self.panel =
something’, then self.panel keeps a reference to the destroyed panel.
Without seeing your code (where you create references, where you destroy

references, etc.), it would be very difficult for us to help you with
fixing such issues.

Thank you, Josiah. I hope I did not imply that I needed a “better” or more complete answer. I was only digging for all the learning I can get. I also did not mean to request help on a specific problem that I haven’t explained or provided any code for. Just looking for leads to further study.

Having said that, here’s my current problem. My program is not referencing or calling destroyed panels or widgets, unless I’m mistake. From what I can gather so far, it’s a class I imported that is doing so.

From wxPyWiki, I borrowed the TextCtrlAutoComplete control. I needed an autocomplete text control without digging too far into wx.STC and Scintilla, and found this control does a pretty good job.

The problem is, TextCtrlAutoComplete hooks itself in pretty good in its .init method:

gp = self
while ( gp != None ) :
gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
gp = gp.GetParent()

self.Bind( wx.EVT_KILL_FOCUS
, self.onControlChanged, self )

I never reference any instance TextCtrlAutoComplete or anything connected to it after its containing frame are

Wow, catching EVT_MOVE and EVT_SIZE for every window above it in the hierarchy. And what does OnControlChanged do? It simply hides the control’s dropdown portion and calls event.Skip(). It seems a bit heavy-handed to have all that event-catching just to make sure the dropdown portion folds up whenever user activity takes place outside the control.

This situation improves if I remove the while loop and its contents – there are then many less dangling references to the control lying around in the hierarchy. However, the way the control is currently written, it sort of relies on those hooks; its dropdown (a wx.PopupWindow) doesn’t follow when the control’s parent is moved or resized.

I’m going to try giving TextCtrlAutoComplete just use a similar while loop to disconnect from all those EVT_MOVE and EVT_SIZE events when it closes. However, according to the page linked below, an occasional EVT_FOCUS is still liable to come through even after the OnClose handler executes, so there are more steps I’ll have to take to safely use this control.

Learning about this brought me to the following page, and a combination of it and your (Josiah’s) suggested approach to just-in-time unsubscribing have been very useful indeed.


wxPyWiki: Surviving with wxEVT_KILL_FOCUS under Microsoft Windows

···

On 6/3/07, Josiah Carlson jcarlson@uci.edu wrote:

<much snipped>

For your convenience, wxPython objects whos C++ object is destroyed
(and thus will raise PyDeadObjectError) evaluate to boolean false. So
you can use if (self): as a guard against access to dead objects.

···

On 6/4/07, Eric Ongerth <ericongerth@gmail.com> wrote:

On 6/3/07, Josiah Carlson <jcarlson@uci.edu> wrote:
>