Implementing a callback in Python – passing a callable reference to the current function

Question:

I want to implement the Observable pattern in Python for a couple of workers, and came across this helpful snippet:

class Event(object):
    pass

class Observable(object):
    def __init__(self):
        self.callbacks = []
    def subscribe(self, callback):
        self.callbacks.append(callback)
    def fire(self, **attrs):
        e = Event()
        e.source = self
        for k, v in attrs.iteritems():
            setattr(e, k, v)
        for fn in self.callbacks:
            fn(e)

Source: Here

As i understand it, in order to subscribe, I would need to pass a callback to the function that is going to be called on fire. If the calling function was a class method, presumably I could have used self, but in the absence of this – how could I directly get a callback that can be useful for the self.callbacks.append(callback) bit?

Asked By: malangi

||

Answers:

Any defined function can be passed by simply using its name, without adding the () on the end that you would use to invoke it:

def my_callback_func(event):
    # do stuff

o = Observable()
o.subscribe(my_callback_func)

Other example usages:

class CallbackHandler(object):
    @staticmethod
    def static_handler(event):
        # do stuff

    def instance_handler(self, event):
        # do stuff

o = Observable()

# static methods are referenced as <class>.<method>
o.subscribe(CallbackHandler.static_handler)

c = CallbackHandler()
# instance methods are <class instance>.<method>
o.subscribe(c.instance_handler)

# You can even pass lambda functions
o.subscribe(lambda event: <<something involving event>>)
Answered By: Amber

To add to Amber’s main answer above, this is a complete example of how two classes can be used to implement a callback from one to another.

I dummied this up to satisfy my own curiosity on whether it would work or not – particularly given that register only seems to be called with a function being passed, but now that I’ve seen this example work, the function must be passed with a context of the object to which it is attached.

class A():
    def register(self,fn):
        self.cb=fn
    
    def gimme(self,val):
        self.cb(val)

class B():
    def __init__(self, o):
        self.o = o
        self.o.register(self.callback)
    
    def callback(self,val):
        print(f"Got called with {val} !!")

    def go(self):
        self.o.gimme('test')

>>> a = A()
>>> b = B(a)
>>> b.go()
Got called with test !!
Answered By: QA Collective