Since you instanciated a B
object, B.__init__
was invoked and added an attribute z
. This attribute is now present in the object. It's not some weird overloaded magical shared local variable of B
methods that somehow becomes inaccessible to code written elsewhere. There's no such thing. Neither does self
become a different object when it's passed to a superclass' method (how's polymorphism supposed to work if that happens?).
There's also no such thing as a declaration that A
objects have no such object (try o = A(); a.z = whatever
), and neither is self
in do
required to be an instance of A
1. In fact, there are no declarations at all. It's all "go ahead and try it"; that's kind of the definition of a dynamic language (not just dynamic typing).
That object's z
attribute present "everywhere", all the time2, regardless of the "context" from which it is accessed. It never matters where code is defined for the resolution process, or for several other behaviors3. For the same reason, you can access a list's methods despite not writing C code in listobject.c
;-) And no, methods aren't special. They are just objects too (instances of the type function
, as it happens) and are involved in exactly the same lookup sequence.
1 This is a slight lie; in Python 2, A.do
would be "bound method" object which in fact throws an error if the first argument doesn't satisfy isinstance(A, <first arg>)
.
2 Until it's removed with del
or one of its function equivalents (delattr
and friends).
3 Well, there's name mangling, and in theory, code could inspect the stack, and thereby the caller code object, and thereby the location of its source code.