Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialNiki Molnar
25,698 PointsWhy do you need to use super()?
When introducing inheritance, the tutor says that by splitting out the Character class initially, you are no longer able to set "sneaky".
The original code was:
class Thief(Character):
sneaky = True
def __init__(self, name, sneaky=True, **kwargs):
self.name = name
self.sneaky = sneaky
for key, value in kwargs.items():
setattr(self, key, value)
The separated code is as follows:
class Character:
def __init__(self, name, **kwargs):
self.name = name
for key, value in kwargs.items():
setattr(self, key, value)
class Thief(Character):
sneaky = True
but if I do the following on the new code:
raffles = Thief("Raffles")
print(raffles.sneaky) # prints True
raffles.sneaky = False
print(raffles.sneaky) # prints False
This changes sneaky without using super() on the Thief class, presumably because it's being passed to Character as a kwarg?
I don't understand why the tutor is saying we can't change sneaky anymore without super()
2 Answers
Alex Koumparos
Python Development Techdegree Student 36,887 PointsHi Niki,
Kenneth's example probably wasn't the best example to illustrate super()
. As you mentioned, the parent class's initialiser can take any number of keyword arguments, so we
have the ability to set any attribute we like on the subclass and super()
seems redundant.
Let's consider instead a totally different example.
When creating a subclass we want to inherit the characteristics and the behaviours of the parent, rather than rewriting them ourselves.
For example:
class Monster:
def __init__(self, name, hungry=True):
self.name = name
self.hungry = hungry
We could implement a Zombie subclass like this:
class Zombie(Monster):
def __init__(self, name, hungry=True):
self.name = name
self.hungry = hungry
self.undead = True
Here we are overriding the initialiser of Monster
. We've repeated ourselves, which is bad, and because we are totally reimplementing the __init__()
method, we're missing out on the benefit of inheritance: delegating behaviour to the parent that we don't need to implement ourselves (and missing out on getting updated behaviour if Monster
gets updated in the future).
So, we could rewrite our Zombie
class as follows:
class Zombie(Monster):
def __init__(self, name, hungry=True):
Monster.__init__(self, name, hungry)
self.undead = True
This is much better, instead of overriding Monster
's initialiser, we are extending it: we're preserving its functionality and relying on it to do whatever needs to be done with name
and hungry
and just implementing our additional functionality: giving the Zombie
the undead
attribute.
However, this code isn't very portable: it depends on the parent class always being Monster
. At a first glance, this is obviously true: it's right there in the class definition. But, we can't guarantee where we (or our future selves) will be in the inheritance hierarchy. Suppose, down the road, someone wants to extend Zombie
to enable new functionality, such as a 'Runner' zombie.
When we (or they) subclass Zombie
to make the extended zombie, we might do this:
class RunnerZombie(Zombie):
surprisingly_fast = True
We haven't overridden or extended Zombie
's __init__()
, so we inherit its initialiser unchanged. We might reasonably assume that RunnerZombie's
initialiser delegates behaviour to its parent (Zombie
) but it doesn't: it delegates behaviour to Monster
. If during the lifecycle of our application, Monster
acquires behaviour that Zombie
shouldn't have (or Zombie
overrides Monster
's behaviour), hardcoding our delegation to Monster
will break RunnerZombie
's behaviour.
Accordingly, we need some keyword that we can use in our class to refer to the immediate parent class instead of hardcoding the name of the class that we think will be the parent. This is why super()
exists.
We can rewrite Zombie
as follows:
class Zombie(Monster):
def __init__(self, name, hungry=True):
super().__init__(self, name, hungry)
self.undead = True
Now future subclasses will get their initialiser behaviour from their immediate parent instead of Monster
.
Hope that clears things up.
Happy coding,
Alex
Alex Koumparos
Python Development Techdegree Student 36,887 PointsAssuming we just want to preserve the existing behaviour, it is fine to leave RunnerZombie as it is. Since it is a child of Zombie and it has no __init__
method of its own, it inherits the __init__
of Zombie.
We only need to use super()
on RunnerZombie when we decide to augment RunnerZombie's behaviour in some way. When that time comes we can give RunnerZombie its own __init__
method, call super()
from inside the __init__
method to pick up Zombie's initialiser and then write our new behaviour. It would look something like this:
def __init__(self):
super() # run the parent's initialiser
self.instantiate_unique_behaviour() # do something unique to RunnerZombie
Hope that clears things up.
Cheers,
Alex
Jamar Slade
2,480 PointsAlex,
These are really great explanations, greatly appreciated.
Tufan T.
3,534 PointsAlex, Excellent details; many thanks for taking the time to write these... Through your notes, I can now understand Super-Duper video much better! Thanks,
Niki Molnar
25,698 PointsNiki Molnar
25,698 PointsAlex, that is amazing!
Thank you so much for taking the time to explain it so well!
Best
Niki
Jaclyn Kandel
7,579 PointsJaclyn Kandel
7,579 PointsThis was very helpful. One question, would the RunnerZombie class require a super() that delegates behavior to Zombie, which delegates behavior to Monster? Or would we use your original RunnerZombie code? Thanks!
Adrian Torrente Tenreiro
12,540 PointsAdrian Torrente Tenreiro
12,540 PointsThanks so much for this explination so Super() is used to extend a class attr in a much safer way without expecifications correct?
Alex Koumparos
Python Development Techdegree Student 36,887 PointsAlex Koumparos
Python Development Techdegree Student 36,887 PointsHi Adrian Torrente Tenreiro
It's not about safety, exactly. It's more about absolute versus relative references. When we use
super()
, such assuper().__init__()
we are referring to the current object's direct parent, which is a relative reference. Compare this toZombie.__init__()
which will always refer absolutely toZombie
's initializer, regardless of where we actually are in the inheritance hierarchy. Neither approach is strictly 'safer' than the other, the correct use depends on context.Hope that's clear.
Cheers
Alex