Re: python classes and signal connects



I think what you are seeing is due to circular references caused by
connecting the bound method, the object of which also holds a ref to
the widget. The connection of self.button_clicked_cb increments the
ref count of the TestSignals object "self", tying its lifetime to that
of the button instance. The button instances lifetime is then also
dependent on that of the TestSignals instance via self.button. These
will show up in the gc.garbage list:
http://docs.python.org/library/gc.html#gc.garbage
To observe this, issue a "del test.button" inside the test loop before
the call to gc.collect() and you should see the print statements.

There are a handful of ways to deal with problems like this:
1) Don't rely on __del__ for cleanup, __del__ should generally be
avoided as it doesn't work like a destructor in most languages, add an
explicit method to cleanup the AddressInfo instance, clear out any
held widgets by setting them to None, also issue the dbus disconnect
within this and even disconnect signals if possible. This is the most
explicit technique given here and likely more readable and
understandable to someone who doesn't know the code base.
2) If possible, avoid holding unnecessary refs to the widgets.
Although if there is a widget hierarchy and the root held, it might
not help.
3) Make the callback a staticmethod and pass self (or precisely the
parts of the state you need) in as a weak reference. Within the
callback you need to verify the user data is not dead (I've never
tried this but it seems like it should work):
    button.connect('pressed', self.button_clicked_cb, weakref.ref(self))
4) A more advanced technique is to use a wrapper to weakly reference
the bound method internals. Connect the callback with something like:
   button.connect('pressed', WeakMethod(self.button_clicked_cb))
See: http://code.activestate.com/recipes/81253-weakmethod/
I also advocate getting this type of feature built into pygobject as I
think it is the cleanest solution, you should be able to do this:
   button.pressed.connect_weak(self.button_clicked_cb)
https://bugzilla.gnome.org/show_bug.cgi?id=332782
* unittests for verifying what you would expect in regards to
reference counts will ease development and maintenance if you don't
already have them. sys.getrefcount and the weakref module are good
resources for doing this.

-Simon

On Tue, Oct 2, 2012 at 10:21 PM, Thomas Bechtold
<thomasbechtold jpberlin de> wrote:
> Hi Simon,
>
> On Mo, 2012-10-01 at 13:44 -0700, Simon Feltman wrote:
>> It would help to give some context as to what you are trying to
>> accomplish with having a class. In the example the TestSignals class
>> is superfluous "t = TestSignals()" could be replaced with the contents
>> of __init__ and the callback could be moved out as a standalone
>> function. But I gather what you are actually trying to do is more
>> complex than this?
>
> I'm trying to port d-feet to pygi and gdbus [1]. The TestSignals() class
> is just an example.
>
>> The specific issue you are seeing is because the callback connected to
>> the signal is a bound method: "self.button_clicked_cb". A bound method
>> will keep a reference to the instance of its class TestSignals and the
>> function. This can be observed as follows:
>>
>> test = TestSignals()
>> test.button_clicked_cb.__self__ == test  # True
>>
>> If button_clicked_cb does not access any state on the TestSignals
>> instance, than by all means it should be moved outside of the class or
>> made into a staticmethod or classmethod.
>
> The method button_clicked_cb() needs access to the class instance.
> So @staticmethod does not help because then I have to provide the instance as
> user_data like this:
>
> def __init__(self):
>         self.button = Gtk.Button("My Button")
>         self.button.connect("clicked", self.button_clicked_cb, self)
>
>     @staticmethod
>     def button_clicked_cb(button, user_data):
>         print "button clicked"
>
>
> And then __del__ is never called, too.
>
>
> Cheers,
>
> Tom
>
>
> [1]
> https://gitorious.org/d-feet/d-feet/blobs/pygi/dfeet/introspection.py
>
>
>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]