I am a beginner in Python and using Lutz's book to understand OOPS in Python. This question might be basic, but I'd appreciate any help. I researched SO and found answers on "how", but not "why."
As I understand from the book, if Sub
inherits Super
then one need not call superclass' ( Super
's) __init__()
method.
Example:
class Super:
def __init__(self,name):
self.name=name
print("Name is:",name)
class Sub(Super):
pass
a = Sub("Harry")
a.name
Above code does assign attribute name
to the object a
. It also prints the name
as expected.
However, if I modify the code as:
class Super:
def __init__(self,name):
print("Inside Super __init__")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
Super(name) #Call __init__ directly
a = Sub("Harry")
a.name
The above code doesn't work fine. By fine, I mean that although Super.__init__()
does get called (as seen from the print statements), there is no attribute attached to a
. When I run a.name
, I get an error, AttributeError: 'Sub' object has no attribute 'name'
I researched this topic on SO, and found the fix on Chain-calling parent constructors in python and Why aren't superclass __init__ methods automatically invoked?
These two threads talk about how to fix it, but they don't provide a reason for why.
Question: Why do I need to call Super
's __init__
using Super.__init__(self, name)
OR super(Sub, self).__init__(name)
instead of a direct call Super(name)
?
In Super.__init__(self, name)
and Super(name)
, we see that Super's __init__()
gets called, (as seen from print statements), but only in Super.__init__(self, name)
we see that the attribute gets attached to Sub
class.
Wouldn't Super(name)
automatically pass self
(child) object to Super
? Now, you might ask that how do I know that self
is automatically passed? If I modify Super(name)
to Super(self,name)
, I get an error message that TypeError: __init__() takes 2 positional arguments but 3 were given
. As I understand from the book, self
is automatically passed. So, effectively, we end up passing self
twice.
I don't know why Super(name)
doesn't attach name
attribute to Sub
even though Super.__init__()
is run. I'd appreciate any help.
For reference, here's the working version of the code based on my research from SO:
class Super:
def __init__(self,name):
print("Inside __init__")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
#Super.__init__(self, name) #One way to fix this
super(Sub, self).__init__(name) #Another way to fix this
a = Sub("Harry")
a.name
PS: I am using Python-3.6.5
under Anaconda Distribution.
As I understand from the book, if Sub inherits Super then one need not call superclass' (
Super
's)__init__()
method.
This is misleading. It's true that you aren't required to call the superclass's __init__
method—but if you don't, whatever it does in __init__
never happens. And for normal classes, all of that needs to be done. It is occasionally useful, usually when a class wasn't designed to be inherited from, like this:
class Rot13Reader:
def __init__(self, filename):
self.file = open(filename):
def close(self):
self.file.close()
def dostuff(self):
line = next(file)
return codecs.encode(line, 'rot13')
Imagine that you want all the behavior of this class, but with a string rather than a file. The only way to do that is to skip the open
:
class LocalRot13Reader(Rot13Reader):
def __init__(self, s):
# don't call super().__init__, because we don't have a filename to open
# instead, set up self.file with something else
self.file = io.StringIO(s)
Here, we wanted to avoid the self.file
assignment in the superclass. In your case—as with almost all classes you're ever going to write—you don't want to avoid the self.name
assignment in the superclass. That's why, even though Python allows you to not call the superclass's __init__
, you almost always call it.
Notice that there's nothing special about __init__
here. For example, we can override dostuff
to call the base class's version and then do extra stuff:
def dostuff(self):
result = super().dostuff()
return result.upper()
… or we can override close
and intentionally not call the base class:
def close(self):
# do nothing, including no super, because we borrowed our file
The only difference is that good reasons to avoid calling the base class tend to be much more common in normal methods than in __init__
.
Question: Why do I need to call
Super's __init__
usingSuper.__init__(self, name)
ORsuper(Sub, self).__init__(name)
instead of a direct callSuper(name)
?
Because these do very different things.
Super(name)
constructs a new Super
instance, calls __init__(name)
on it, and returns it to you. And you then ignore that value.
In particular, Super.__init__
does get called one time either way—but the self
it gets called with is that new Super
instance, that you're just going to throw away, in the Super(name)
case, while it's your own self
in the super(Sub, self).__init__(name)
case.
So, in the first case, it sets the name
attribute on some other object that gets thrown away, and nobody ever sets it on your object, which is why self.name
later raises an AttributeError
.
It might help you understand this if you add something to both class's __init__
methods to show which instance is involved:
class Super:
def __init__(self,name):
print(f"Inside Super __init__ for {self}")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
print(f"Inside Sub __init__ for {self}")
# line you want to experiment with goes here.
If that last line is super().__init__(name)
, super(Sub, self).__init__name)
, or Super.__init__(self, name)
, you will see something like this:
Inside Sub __init__ for <__main__.Sub object at 0x10f7a9e80>
Inside Super __init__ for <__main__.Sub object at 0x10f7a9e80>
Notice that it's the same object, the Sub
at address 0x10f7a9e80, in both cases.
… but if that last line is Super(name)
:
Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0>
Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0>
Now we have two different objects, at different addresses 0x10f7a9ea0 and 0x10f7a9ec0, and with different types.
If you're curious about what the magic all looks like under the covers, Super(name)
does something like this (oversimplifying a bit and skipping over some steps 1 ):
_newobj = Super.__new__(Super)
if isinstance(_newobj, Super):
Super.__init__(_newobj, name)
… while super(Sub, self).__init__(name)
does something like this:
_basecls = magically_find_next_class_in_mro(Sub)
_basecls.__init__(self, name)
As a side note, if a book is telling you to use super(Sub, self).__init__(name)
or Super.__init__(self, name)
, it's probably an obsolete book written for Python 2.
In Python 3, you just do this:
super().__init__(name)
: Calls the correct next superclass by method resolution order. You almost always want this. super(Sub, self).__init__(name)
: Calls the correct next superclass—unless you make a mistake and get Sub
wrong there. You only need this if you're writing dual-version code that has to run in 2.7 as well as 3.x. Super.__init__(self, name)
: Calls Super
, whether it's the correct next superclass or not. You only need this if the method resolution order is wrong and you have to work around it. 2 If you want to understand more, it's all in the docs, but it can be a bit daunting:
__new__
__init__
super
(also see Raymond Hettinger's blog post ) The original introduction to super
, __new__
, and all the related features was very helpful to me in understanding all of this. I'm not sure if it'll be as helpful to someone who's not coming at this already understanding old-style Python classes, but it's pretty well written, and Guido (obviously) knows what he's talking about, so it might be worth reading.
1. The biggest cheat in this explanation is that super
actually returns a proxy object that acts like _baseclass
bound to self
in the same way methods are bound, which can be used to bind methods, like __init__
. This is useful/interesting knowledge if you know how methods work, but probably just extra confusion if you don't.
2. … or if you're working with old-style classes, which don't support super
(or proper method-resolution order). This never comes up in Python 3, which doesn't have old-style classes. But, unfortunately, you will see it in lots of tkinter examples, because the best tutorial is still Effbot's, which was written for Python 2.3, when Tkinter was all old-style classes, and has never been updated.
Super(name)
is not a "direct call" to the superclass __init__
. After all, you called Super
, not Super.__init__
.
Super.__init__
takes an uninitialized Super
instance and initializes it. Super
creates and initializes a new, completely separate instance from the one you wanted to initialize (and then you immediately throw the new instance away). The instance you wanted to initialize is untouched.
Super(name)
instantiates a new instance of super. Think of this example:
def __init__(self, name):
x1 = Super(name)
x2 = Super("some other name")
assert x1 is not self
assert x2 is not self
In order to explicitly call The Super
's constructor on the current instance, you'd have to use the following syntax:
def __init__(self, name):
Super.__init__(self, name)
Now, maybe you don't want read further if you are a beginner.
If you do, you will see that there is a good reason to use super(Sub, self).__init__(name)
(or super().__init__(name)
in Python 3) instead of Super.__init__(self, name)
.
Super.__init__(self, name)
works fine, as long as you are certain that Super
is in fact your superclass. But in fact, you don't know ever that for sure.
You could have the following code:
class Super:
def __init__(self):
print('Super __init__')
class Sub(Super):
def __init__(self):
print('Sub __init__')
Super.__init__(self)
class Sub2(Super):
def __init__(self):
print('Sub2 __init__')
Super.__init__(self)
class SubSub(Sub, Sub2):
pass
You would now expect that SubSub()
ends up calling all of the above constructors, but it does not:
>>> x = SubSub()
Sub __init__
Super __init__
>>>
To correct it, you'd have to do:
class Super:
def __init__(self):
print('Super __init__')
class Sub(Super):
def __init__(self):
print('Sub __init__')
super().__init__()
class Sub2(Super):
def __init__(self):
print('Sub2 __init__')
super().__init__()
class SubSub(Sub, Sub2):
pass
Now it works:
>>> x = SubSub()
Sub __init__
Sub2 __init__
Super __init__
>>>
The reason is that the super class of Sub
is declared to be Super
, in case of multiple inheritance in class SubSub
, Python's MRO establishes the inheritance as: SubSub
inherits from Sub
, which inherits from Sub2
, which inherits from Super
, which inherits from object
.
You can test that:
>>> SubSub.__mro__
(<class '__main__.SubSub'>, <class '__main__.Sub'>, <class '__main__.Sub2'>, <class '__main__.Super'>, <class 'object'>)
Now, the super()
call in constructors of each of the classes finds the next class in the MRO so that the constructor of that class can be called.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.