3

When defining methods in a class we do have an argument for the object on which the method is invoked i.e self.

Say I have a class Foo, and an instance of it:

class Foo:
   def m(self):
       pass

foo = Foo()

and we do something like this:

foo_m = foo.m

and then try calling foo_m with the same parameters the way would have done with foo.m. It just works.

So how does foo_m knows what should be the value of the self argument when it is not being called in foo.m() fashion?

When printing the foo.m, the REPL shows that its a bound method to that particular object.

Is it some other function object that keeps track of the self argument and the method to be called?

3
  • foo_m is bound to foo.m and therefor also to the fooobject, when it is called it internally uses foo as the self parameter. Commented Oct 28, 2015 at 7:29
  • 3
    You can think of it like a functools.partial, where self is already set to the instance and you just supply the other parameters.
    – jonrsharpe
    Commented Oct 28, 2015 at 7:50
  • Isn't foo_m just s reference to the original object? Commented Oct 29, 2015 at 3:54

2 Answers 2

4

A bound method is just a Python object that stores self in one of its slots. Take a look at methodobject.c for the C implementation. In Python the slot is accessible via the __self__ attribute:

>>> a = [1, 2, 3].append
>>> a
<built-in method append of list object at 0x103b39588>
>>> a.__self__
[1, 2, 3]
1

This is because of the descriptor protocol (the same that underlies @property).

Very simply*, if you do foo.m, it works roughly like calling getattribute(foo, 'm') in the following pseudocode:

def getattribute(obj, attr):
    if attr in obj.__dict__:
        # instance variable
        return obj.__dict__[attr]

    # class variable
    for cls in type(obj).__mro__:
        # MRO: method resolution order, sequence of the type and all its superclasses
        if attr in cls.__dict___:
            value = cls.__dict__[attr]
            break
        else:  # no break
            raise AttributeError(attr)

    if hasattr(value, '__get__'):
        # descriptor protocol gets invoked!
        return value.__get__(obj, type(obj))

    # regular class variable
    return value

* This is only valid if obj is not a type, and if there is no __getattr__ or __getattribute__ involved, but I'm ignoring those possibilities because they complicate the story but aren't very relevant for your particular question.

Additionally, any occurrence of attribute access inside of getattribute is actually not regular attribute access, so there's no infinite regression.

Methods work because all Python functions are descriptors themselves, and they work a bit like this:

class FunctionType:
    def __call__(self, *args, **kwargs):
        ... # don't think about this one too hard
    def __get__(self, instance, class_):
        if instance is None:  # Foo.m
            return self
        return BoundFunction(__func__=self, __self__=instance) # foo.m

@dataclass
class BoundFunction:
    __func__: FunctionType
    __self__: Any
    def __call__(self, *args, **kwargs):
        # because self.__func__ is an instance variable, it doesn't invoke the descriptor protocol!
        return self.__func__(self.__self__, *args, **kwargs)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.