Design thought for callbacks

Chris Angelico rosuav at gmail.com
Sat Feb 21 09:36:35 EST 2015


On Sun, Feb 22, 2015 at 1:07 AM, Cem Karan <cfkaran2 at gmail.com> wrote:
> I agree about closures; its the only way they could work.  When I was originally thinking about the library, I was trying to include all types of callbacks, including closures and callable objects.  The callable objects may pass themselves, or one of their methods to the library, or may do something really weird.
>
> Although I just realized that closures may cause another problem.  In my code, I expect that many different callbacks can be registered for the same event.  Unregistering means you request to be unregistered for the event. How do you do that with a closure?  Aren't they anonymous?
>

They're objects, same as any other, so the caller can hang onto a
reference and then say "now remove this one". Simple example:

callbacks = []
def register_callback(f): callbacks.append(f)
def unregister_callback(f): callbacks.remove(f)
def do_callbacks():
    for f in callbacks:
        f()

def make_callback(i):
    def inner():
        print("Callback! %d"%i)
    register_callback(inner)
    return inner

make_callback(5)
remove_me = make_callback(6)
make_callback(7)
unregister_callback(remove_me)
do_callbacks()

The other option is for your callback registration to return some kind
of identifier, which can later be used to unregister the callback.
This is a good way of avoiding reference cycles (the ID could be a
simple integer - maybe the length of the list prior to the new
callback being appended, and then the unregistration process is simply
"callbacks[id] = None", and you skip the Nones when iterating), and
even allows you to register the exact same function more than once,
for what that's worth.

When I do GUI programming, this is usually how things work. For
instance, I use GTK2 (though usually with Pike rather than Python),
and I can connect a signal to a callback function. Any given signal
could have multiple callbacks attached to it, so it's similar to your
case. I frequently depend on the GTK engine retaining a reference to
my function (and thus to any data it requires), as I tend not to hang
onto any inner objects that don't need retention. Once the parent
object is destroyed, all its callbacks get dereferenced. Consider this
simplified form:

def popup_window():
    w = Window()
    # Add layout, info, whatever it takes
    btn = Button("Close")
    w.add(btn) # actually it'd be added to a layout
    btn.signal_connect("clicked", lambda *args: w.destroy())

The GUI back end will hang onto a reference to the window, because
it's currently on screen; to the button, because it's attached to the
window; and to my function, because it's connected to a button signal.
Then when you click the button, the window gets destroyed, which
destroys the button, which unregisters all its callbacks. At that
point, there are no refs to the function, so it can get disposed of.
That button function was the last external reference to the window,
and now that it's not on screen, its Python object can also be
disposed of, as can the button inside. So it'll all clean up fairly
nicely; as long as the callback gets explicitly deregistered, that's
the end of everything.

ChrisA



More information about the Python-list mailing list