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?

Alexander
5,1951 gold badge24 silver badges28 bronze badges
asked Oct 28, 2015 at 6:00
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. 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]
answered Nov 1, 2015 at 14:16
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)
answered Mar 30, 2024 at 20:55

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.