Python Instance Method References

published on 2015-04-14

So, I came across something pretty interesting while coding in Python today. I was writing a system in which I have to pass a function as an argument to be called later; a.k.a. a callback. I’ve done this in a lot of different contexts in languages like Python, PHP, JavaScript, Java, and ActionScript, so that’s why I was taken aback by my experience today.

I have one class which takes a function or a list of functions for calling later; the signature of the function will look like this:

# defining the callback
def my_callback(value):
    # do something
    
# adding it
self.callbacks = [my_callback]

# executing the callbacks
for c in self.callbacks:
    c(some_value)

Originally, I was just defining a bunch of these functions in a separate module; they’re all generic functions, not in classes, or anything. Everything worked great.

But then I decided to start passing in a method on an instance for the callback. Since I needed some of the state of the instance during the callback, I decided to modify the signature to add that context.

# defining the callback
def my_callback(self, value):
    # do something

# adding it
self.callbacks = [self.my_callback]

# executing the callbacks
for c in self.callbacks:
    c(self, some_value)

So now, even though I don’t need the context in the module functions, I’ll get it in the instance functions.

Or so I thought.

It turns out that, by doing this, I ended up passing in the self context object twice because Python does something pretty clever: when providing a reference like self.my_callback, the self context becomes a part of the callback. Therefore, I didn’t have to change the function signature at all, and the instance method will get both the self and value args. Looking into it, the same thing happens when calling things from a class context, say if I were to pass MyClass.my_callback instead of self.my_callback.

This is very clever, but not immediately clear and is different from other languages I’ve done this in. Then again, in other languages, the context (self in the case of an instance method, cls in the case of a class method, or nothing in the case of a static function) is not explicitly part of the function signature and is only implied by certain keywords when defining the function… mainly just the inclusion or exclusion of static since I’m not aware of a distinction between static and class functions in anything but python.

The reason it does this is due to method binding. If a function has self as its first argument and the function is referenced on an instance of the class, then it’s bound to the object. You can have an unbound version of the function, say if you used MyClass.instance_method instead of referencing it on an instance of MyClass; it won’t work properly however, because the function requires us to have an instance of the object as the first argument.

The same kind of thing happens when we declare the class method, only this time, it’s bound to the class, rather than the object. As best as I can tell, you can’t have an unbound class method because the class is always available if the function is. In the case of a static function, it isn’t bound to anything; it is more or less just namespaced on the class; as such, it is neither bound nor unbound.

So, what does bound mean? It means that, when the function is called, it already has the thing to which it’s bound and it will be passed into the function as the first argument (the self for instance methods, the cls for class methods). This is just another example of functions being first-class objects; they each contain the state relevant to their execution. Very cool!

Therefore, using the above knowledge, I can have something like this:

callbacks = []
def add_callback(callback):
    callbacks.append(callback)

def do_thing_function(*args):
    print args

class MyObject(object):
    def do_thing_instance(*args):
        print args
    
    @classmethod
    def do_thing_class(*args):
        print args
    
    @staticmethod
    def do_thing_static(*args):
        print args

add_callback(do_thing_function)

o = MyObject()
add_callback(o.do_thing_instance)
add_callback(MyObject.do_thing_instance)
add_callback(MyObject.do_thing_class)
add_callback(MyObject.do_thing_static)

value = "stringy value"
for c in callbacks:
    print "calling function %s" % c.__name__
    c(value)

# output
calling function do_thing_function
('stringy value',)
calling function do_thing_instance
(<__main__.MyObject object at 0x10cc6e350>, 'stringy value')
calling function do_thing_class
(<class '__main__.MyObject'>, 'stringy value')
calling function do_thing_static
('stringy value',)

As you can see, we get different things in the function body, even though we’re only explicitly passing in one value at time of execution. Yay contextual function execution!