Reputation: 349
I came across weird behavior in Python 3.6. I was able to call function and access variable defined only in child class from base class method. I find this useful in my code but I come from C++ and this code looks very weird. Can someone please explain this behavior?
class a:
def __init__(self):
print(self.var)
self.checker()
class b(a):
def __init__(self):
self.var=5
super().__init__()
def checker(self):
print('inside B checker')
myB = b()
Output:
5
inside B checker
Upvotes: 1
Views: 674
Reputation: 365995
All methods in Python are looked up dynamically. You're calling a method on self
, and self
is a b
instance, and b
instances have a checker
method, so that method gets called.
Consider this code at the module top level, or in a top-level function:
myB = b()
myB.checker()
Obviously the global module code isn't part of the b
class definition, and yet, this is obviously legal. Why should it be any different if you put the code inside the class a
definition, and rename myB
to welf
? Python doesn't care. You're just asking the value—whether you've called it myB
or self
—"do you have something named checker
?", and the answer is yes, so you can call it.
And var
is even simpler; self.var
just adds var
to self.__dict__
, so it's there; the fact that it's a b
instance isn't even relevant here (except indirectly—being a b
instance means it had b.__init___
called n it, and that's where var
was created).
If you're wondering how this "asking the value", a slightly oversimplified version is:
__dict__
. When you do self.var=5
, that actually does self.__dict__['var'] = 5
. And when you print(self.var)
, that does print(self.__dict__['var'])
.KeyError
, as it will for self.checker
, Python tries type(self).__dict__['checker']
, and, if that doesn't work, it loops over type(self).mro()
and tries all of those dicts.KeyError
, as they would with self.spam
, Python calls self.__getattr__('spam')
.AttributeError
.Notice that if you try to construct an a
instance, this will fail with an AttributeError
. That's because now self
is an a
, not a b
. It doesn't have a checker
method, and it hasn't gone through the __init__
code that adds a var
attribute.
The reason you can't do this in C++ is that C++ methods are looked up statically. It's not a matter of what type the value is at runtime, but what type the variable is at compile time. If the statically looked-up method says it's virtual, then the compiler inserts some dynamic-lookup code, but otherwise, it doesn't.1
One way it's often explained is that in Python (and other languages with SmallTalk semantics, like ObjC and Ruby), all methods are automatically virtual. But that's a bit misleading, because in C++, even with virtual methods, the method name and signature still has to be findable on the base class; in Python (and SmallTalk, etc.), that isn't necessary.
If you're thinking this must be horribly slow, that Python must have to do something like search some stack of namespaces for the method by name every time you call a method—well, it does that, but it's not as slow as you've expect. For one thing, a namespace is a dict, so it's a constant-time search. And the strings are interned and have their hash values cached. And the interpreter can even cache the lookup results if it wants to. The result is still slower than dereferencing a pointer through a vtable, but not by a huge margin (and besides, there are plenty of other things in Python that can be 20x slower than C++, like for
loops; you don't use pure Python when you need every detail to work as fast as possible).
1. C++ also has another problem: even if you defined a var
attribute and a checker
virtual method in a
, you don't get to choose the order the initializers get called; the compiler automatically calls a
's constructor first, then b
's. In Python, it calls b.__init__
, and you choose when you want to call super().__init__()
, whether it's at the start of the method, at the end, in the middle, or even never.
Upvotes: 2
Reputation: 364
First you are creating an instance of class b
which will call the constructor of class b __init__
.
Inside the constructor your are setting the attribute self.var
as 5.Later super().__init__()
will call the constructor of the parent class A.
Inside the constructor of class A
both self.var
is printed and self.checker()
is called.
Note that when calling the super().__init__()
will place the child class instance self
as the first argument by default.
Upvotes: 0