I am wondering how self works under-the-hood in Python classes. My current limited understanding is that when a class is defined, e.g. class Foo: def __init__(self, x: int): self.x = x def bar(self, a: float): print(self.x) print(a) we get Foo in the current environment, and this object contains the methods attached to the class definition, in this case bar. When we create an instance, e.g. foo = Foo(1) We now have both foo and Foo, the former containing the assigned fields, and the latter containing the methods. So now when I call: foo.bar(3.14) a lookup to find bar is performed, first in foo.__dict__, and then, failing that, in Foo.__dict__. Once bar is found in Foo.__dict__, it is called roughly like: Foo.__dict__["bar"](foo, 3.14) Is this an accurate explanation of what happens? And if so, how is foo actually passed to the method? Is it a via a pointer to the instance? I am a bit confused about how PyObject and PyTypeObject fit into this. Continue reading...