There's really nothing to be confused about here.
We have a function that says "when you call foo
with a self
parameter, look up moo
in self
's namespace, assign that value to printer
in self
's namespace, look up printer
in self
's namespace, and call that value".1
Unless/until you call that function, it doesn't matter whether or not anyone anywhere has an attribute named moo
.
When you do call that method, whatever you pass as the self
had better have a moo
attribute or you're going to get an AttributeError
. But this is no different from looking up an attribute on any object. If you write def spam(n): return n.bit_length()
as a global function, when you call that function, whatever you pass as the n
had better have a bit_length
attribute or you're going to get an AttributeError
.
So, we're calling it as test.foo()
, so we're passing test
as self
. If you know how attribute lookup works (and there are already plenty of questions and answers on SO about that), you can trace this through. Slightly oversimplified:
- Does
test.__dict__
have a 'moo'
? No.
- Does
type(test).__dict__
have a 'moo'
? Yes. So we're done.
Again, this is the same way we check if 3
has a bit_length()
method; there's no extra magic here.
That's really all there is to it.
In particular, notice that test.__dict__
does not have a 'moo'
. Methods don't get created at construction time (__new__
) any more than they get created at initialization time (__init__
). The instance doesn't have any methods in it, because it doesn't have to; they can be looked up on the type.2
Sure, we could get into descriptors, and method resolution order, and object.__getattribute__
, and how class
and def
statements are compiled and executed, and special method lookup to see if there's a custom __getattribute__
, and so on, but you don't need any of that to understand this question.
1. If you're confused by this, it's probably because you're thinking in terms of semi-OO languages like C++ and its descendants, where a class has to specify all of its instances' attributes and methods, so the compiler can look at this->moo()
, work out that this has a static type of
Foo, work out that
moois the third method defined on
Foo, and compile it into something like
this->vptr2`. If that's what you're expecting, forget all of it. In Python, methods are just attributes, and attributes are just looked up, by name, on demand.
2. If you're going to ask "then why is a bound method not the same thing as a function?", the answer is descriptors. Briefly: when an attribute is found on the type, Python calls the value's __get__
method, passing it the instance, and function objects' __get__
methods return method objects. So, if you want to refer specifically to bound method objects, then they get created every time a method is looked up. In particular, the bound method object does not exist yet when we call foo
; it gets created by looking up self.moo
inside foo
.