"You Have Your Mother's Eyes" • Inheritance in Python Classes (Harry Potter OOP Series #5)
Year 5 at Hogwarts School of Codecraft and Algorithmancy • Inheritance
The Ancient Library shimmered under a sea of astral lanterns, their cool silver light bathing the earnest faces of fifth-year students. In their hands, a book thrummed with the aura of arcane secrets and ancestral wisdom:
Ethereal Heirlooms: Unraveling Inheritance and Harnessing Hierarchies
Last year's expedition had led them through the intricate labyrinth of class interactions, turning these budding wizards into adept Python architects. The hushed whispers of their former lessons lingered in the air, dancing with the dust motes amongst rows of timeworn tomes, serving as a poignant reminder of the path they had trodden.
The forthcoming chapter of their quest held the keys to the grand kingdom of inheritance, a kingdom of castles built upon the foundations of those that came before. Each snippet of code, each method was a stepping stone leading to a more complex citadel of interconnected classes.
Fifth-years, as the Ancient Library's celestial glow dances on the parchment before you, ready yourselves for another deep dive into Python's enchanting world. A chronicle of further learning, unveiling, and mastery is on the horizon.
Students learn about inheritance in the fifth year at Hogwarts School of Codecraft and Algorithmancy.
[Spoiler Alert: There are spoilers from the final Harry Potter book in the next paragraph]
"You have your mother's eyes" is a recurring phrase in the Harry Potter series that seems appropriate as the title for this article about inheritance. Many wizards who knew Harry's mother said that phrase to Harry at some stage. These words were famously also Snape's last words to Harry. Except that…they weren't. Not in the books, in any case. Snape's last words in the book were "Look...at...me". But the reference may have been too subtle for the films. So, in the film, this changed to "Look at me. You have your mother's eyes".
The Curriculum at Hogwarts (Series Overview)
This is the fifth in a series of seven articles, each linked to a year at Hogwarts School of Codecraft and Algorithmancy:
Year 1 is designed to ensure the new students settle into a new school and learn the mindset needed for object-oriented programming
Year 2: Students will start defining classes and learning about data attributes
Year 3: It's time to define methods in the classes
Year 4: Students build on their knowledge and learn about the interaction between classes
Year 5 (this article): Inheritance. The students are ready for this now they're older
Year 6: Students learn about special methods. You may know these as dunder methods
Year 7: It's the final year. Students learn about class methods and static methods
Professors Are Wizards. So Are The Students
Over the past few years at Hogwarts School of Codecraft and Algorithmancy, you built a set of classes to deal with the wizarding world. The classes represent the "things" that describe the situation. So far, you have the following classes:
WizardHouseWandSpell
Each of these classes has its own attributes—data attributes and methods. The data attributes contain the information needed by the objects. The methods give the objects the functionality to do things with the data.
Here's the code with all the classes you have so far:
There are professors and students at Hogwarts School of Codecraft and Algorithmancy. There's different information that's relevant to the two categories. Teachers teach certain subjects and mark exams, for example. Students study several subjects and take exams.
Therefore, we need different classes to represent professors and students. However, professors and students are also wizards. The new classes to represent professors and students should also have the attributes from the Wizard class you defined in previous years.
It would be wasteful to define two separate classes for professors and students with lots of code in common. That goes against the DRY principle—Don't Repeat Yourself.
Instead, you can create two new classes, Professor and Student, that inherit from Wizard:
All professors are wizards. But not all wizards are professors.
All students are wizards. But not all wizards are students.
When you create a class that inherits from another one, the child class starts off having the same attributes as the parent class. Then, you can add attributes or even change some of the existing ones, as you'll see in this year's curriculum.
Note: several terms can be used to refer to classes when dealing with inheritance. When a class inherits from another class, it can be called a subclass, a derived class or a child class. The class it inherits from can be called a superclass, a base class or a parent class. I'll mostly use the terms 'parent class' and 'child class' as I think they're the most intuitive at this stage.
Create Professor That Inherits From Wizard
Let's create a Professor class. I'm also showing the Wizard class in full in the following code segment:
Let's look at the differences between defining the Wizard and Professor classes:
The class name
Professoris followed by parentheses containing the name of another class. This shows thatProfessorinherits fromWizard. It has access to the same data attributes and methods.The
Professorclass has its own__init__()method. It includes the parametersself,name,patronus, andbirth_year. But there's alsosubject, which is an additional parameter.There's a "strange" line of code in the
__init__()method that starts withsuper(). We'll go through what this line does shortly.Professorhas an additional data attribute,.subject. You assign the value from the argumentsubjectto this data attribute.
Let's first look at this short example to understand inheritance at a top level:
The ParentClass object is an instance of ParentClass but not of ChildClass. However, since ChildClass inherits from ParentClass, the ChildClass object is an instance of both classes.
Let's return to the Professor class in the main code and focus on the following line of code:
super().__init__(name, patronus, birth_year)This calls the superclass's __init__() method. In this case, Professor inherits from Wizard. Therefore, this line calls Wizard.__init__(). This call includes the parameters name, patronus, and birth_year, but not subject since that's not a parameter in the Wizard class.
An instance of Professor is also an instance of Wizard. Therefore, when you initialise a Professor instance using its __init__() method, you also initialise it as a Wizard instance first.
We can have more complex inheritance setups, and super() will also handle these cases. However, in this series, we won't explore these cases.
Adding and overriding methods in a subclass
A child class starts off with the same attributes as the parent class. You've already seen how to add new data attributes, such as .subject. You can also add methods to the child class that are not present in the parent class:
The Professor class now has a method called assess_student() that's unique to this child class and doesn't exist in the parent class. You'll finish writing this method shortly.
You can add new methods to a child class, as you did with assess_student(). But you can also change methods that exist in the parent class:
The Wizard class has its own assign_wand() method. However, you define another assign_wand() as part of the Professor subclass. This new method overrides the one in the parent class. So, when you call this method on a Professor object, the method defined in Professor is called.
In this case, the first line in Professor.assign_wand() is:
super().assign_wand(wand)You've already seen super() used. This line calls the assign_wand() method of the superclass, which is Wizard. Therefore the method in the child class, Professor, will first do whatever the method in the parent class does, but it will also perform additional tasks. In this case, it increases the professor's skill.
If you want the new method to be significantly different from the one in the parent class, you don't need to use super() to call the parent's method. Whether you want to build on the parent's method depends on what you need your method to do.
Create Student That Inherits from Wizard
Next, you can create the Student class. Since a student at Hogwarts School of Codecraft and Algorithmancy is also a wizard, this new class also inherits from Wizard:
You know what to expect. Since Student inherits from Wizard, you'll see some similarities with how you dealt with the Professor class earlier:
You define the class using the line
class Student(Wizard). When you addWizardwithin parentheses after theclasskeyword and the new class's name, you show thatStudentinherits fromWizard.You define
Student.__init__():The special method has five parameters, including
self. There's an additional parameter namedschool_yearcompared to the parent class's__init__()method.You call
super().__init__()from withinStudent.__init__()to initialise the object as aWizardas well as aStudent.The final lines in
__init__()create two new data attributes to store the school year and the student's grades. The.subject_gradesdata attribute is a dictionary, so it can store the subject name as a key and the test scores as values.
You define three new methods unique to the
Studentclass. These methods:Assign a house to the student
Allow the student to take an exam
Assign subjects that the student is taking at school
The first of the three methods is assign_house_using_sorting_hat(). We'll get back to this method in the final year.
The second method is take_exam(), which takes the subject name and the grade as arguments. This method updates the .subject_grades data attribute, which is a dictionary.
The third method, assign_subjects(), accepts an iterable with the names of the subjects the student is learning and creates the dictionary with all the subjects. Initially, None is the value associated with all subject names.
Caveat: These methods are not perfect. We could discuss ways of improving them to make them more robust. However, the aim of this series of articles is not to create the perfect software to solve Hogwarts' administrative problems. Instead, we want to ensure we understand how classes work.
Complete Professor.assess_student()
It's time to return to the Professor class and write the code for the assess_student() method:
Since you have objects of type Student, you can pass these objects to Professor.assess_student(). This is the value associated with the student parameter in assess_student().
You call this method when a professor needs to assess a student. The method first checks whether the subject the professor teaches, self.subject, is one of the subjects the student is learning—these subjects are stored in the student.subject_grades dictionary.
If the student is learning the subject, the method calls student.take_exam(). You pass the subject taught by the professor, self.subject, and the grade. Recall that self refers to the Professor instance in this case since you're using it within the Professor class. You'll look at this method call in more detail later in this article.
Let's Try These New Classes
It's time to use these new subclasses to see whether they work as intended. You can return to the making_magic.py script you used in previous years to test these classes:
Let's look at all the steps included in this script:
You create two
Studentobjects,harryandhermione. Note that in previous versions of this script from previous years, these objects were instances ofWizard. Now that you have aStudentsubclass, you use this new class.You create two
Houseobjects,gryffindorandslytherin.You create two
Professorobjects,snapeandmcgonagall. You callassign_wand()andassign_house()for both instances. Note that theProfessorclass doesn't have a distinctassign_house()defined for the class. However, it inherits the method fromWizard, which is the parent class. The text displayed when you assign a wand to a wizard still has some issues. You'll deal with these in Year 6.Next, you assign two subjects to
harry. You don't assign any subjects tohermione.You call
snape.assess_student()twice, once for each student. Since Harry Potter studies Potions, the method assigns a grade to the instanceharry. However, the instancehermionedoesn't include"Potions"as one of the subjects.
I'm using strings to represent subject names in this example. I don't want the code in this series to get out of hand. However, you may want to consider creating a Subject class to deal with subjects studied at Hogwarts School of Codecraft and Algorithmancy.
Following The Trail (But You Don't Need To)
Let's look at the line in which Snape assesses Harry and gives him a low mark!
snape.assess_student(harry, 20)Note that this line doesn't have any reference to the subject. This is because, at Hogwarts School of Codecraft and Algorithmancy, each teacher only teaches one subject (the same as the Hogwarts in the original Harry Potter stories.)
However, this line changes Harry Potter's Potions grade:
The value for "Potions" in harry.subject_grades changes from None to 20 after the call to snape.assess_student(harry, 20).
Let's follow the trail:
snape.assess_student(harry, 20)snape
This refers to theProfessorobject.snape.assess_student
We access one of theProfessorobject's attributes: the methodassess_student().snape.assess_student(harry, 20)
You pass theStudentinstanceharryand the integer20to this method.
You'll recall from earlier years at Hogwarts that this call, snape.assess_student(harry, 20), is equivalent to:
Professor.assess_student(snape, harry, 20)The Professor instance is passed to the self parameter, which is the first parameter in all instance methods. Therefore, the method has access to the objects snape and harry. And the integer 20, of course!
This means the method also has access to all the attributes that belong to snape and harry, including the subject taught by the professor.
Let's zoom in on the following line of code in the Professor.assess_student() method:
student.take_exam(self.subject, grade)In this example, this is equivalent to the following:
harry.take_exam(snape.subject, grade)Or:
harry.take_exam("Potions", 20)Our journey now takes us to the Student.take_exam() method. The call above is equivalent to:
Student.take_exam(harry, "Potions", 20)The take_exam() method has the following line of code:
self.subject_grades[subject] = gradeThis is equivalent to:
harry.subject_grades["Potions"] = 20This updates the value associated with the key "Potions" in the dictionary.
Object-oriented programming allows you to abstract all this functionality within the objects. So, when you call snape.assess_student(harry, 20), you don't need to worry about what happens behind the scenes. Whatever is needed for Snape to assess Harry and assign him a grade of 20% will happen out of sight.
Terminology Corner
Subclass or Derived Class or Child Class: A class that inherits from another one
Superclass or Base Class or Parent Class: A class from which other classes inherit
"You have your mother's eyes". Harry Potter clearly inherited his mother's eyes. We all inherit characteristics from our parents, but we also have our own characteristics. We're distinct from our parents. This is similar to inheritance in classes. Child classes inherit attributes from their parent classes, but they can change those attributes and have new ones.
This brings us to the end of Year 5. Enjoy the holidays. Year 6 is special. Literally. You'll learn about special methods (aka dunder methods).
Next article in this series: Year 6 • Time for Something Special • Special Methods in Python Classes
Code in this article uses Python 3.11
Stop Stack
Recently published articles on The Stack:
Casting A Spell • More Interaction Between Classes. Year 4 at Hogwarts School of Codecraft and Algorithmancy • More on Methods and Classes
An Object That Contains Objects • Python's Containers. Containers • Part 4 of the Data Structure Categories Series
Zen and The Art of Python
turtleAnimations • A Step-by-Step Guide. Python'sturtleis not just for drawing simple shapesThe One About The Taxi Driver, Mappings, and Sequences • A Short Trip to 42 Python Street. How can London cabbies help us learn about mappings and sequences in Python?
Deconstructing Ideas And Constructing Code • Using the Store-Repeat-Decide-Reuse Concept. Starting to code on a blank page • How do you convert your ideas into code?
I've added a new page on The Python Coding Stack's homepage called The Python Series—that's ‘series’ as a plural noun! It's a shame ‘series’ is a word that has the same singular and plural form. For the avid readers, you know that I publish several stand-alone articles, but I also publish articles that are part of series (pl.), such as this article you just finished reading. I'll use this page to keep all series in one place. At the moment, there are links to The Data Structure Category Series and The Harry Potter Object-Oriented Programming Series. I'll add more links to future series here, too.
The Python Coding Stack is now two months old. This is the 14th article I'm publishing and I'm pleased that so many of you are finding these articles useful. There are over 500 of you subscribed to this Substack now, and I know many more read these articles, too. I enjoy writing. And I'm glad that there are people who enjoy reading what I write, too. Thank you.
Most articles will be published in full on the free subscription. However, a lot of effort and time goes into crafting and preparing these articles. If you enjoy the content and find it useful, and if you're in a position to do so, you can become a paid subscriber. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles.



![# hogwarts_magic.py import random class Wizard: def __init__(self, name, patronus, birth_year): self.name = name self.patronus = patronus self.birth_year = birth_year self.house = None self.wand = None self.skill = 0.2 # 0.0 (bad) to 1.0 (good) def increase_skill(self, amount): self.skill += amount if self.skill > 1.0: self.skill = 1.0 def assign_wand(self, wand): self.wand = wand print(f"{self.name} has a {self.wand} wand.") def assign_house(self, house): self.house = house house.add_member(self) def cast_spell(self, spell): if self.wand: effect = self.wand.cast_spell(spell, self) if effect: print(f"{self.name} cast {effect}!") else: print(f"{self.name} failed to cast {spell.name}!") else: print(f"{self.name} has no wand!") class House: def __init__(self, name, founder, colours, animal): self.name = name self.founder = founder self.colours = colours self.animal = animal self.members = [] self.points = 0 def add_member(self, member): if member not in self.members: self.members.append(member) def remove_member(self, member): self.members.remove(member) def update_points(self, points): self.points += points def get_house_details(self): return { "name": self.name, "founder": self.founder, "colours": self.colours, "animal": self.animal, "points": self.points } class Wand: def __init__(self, wood, core, length, power=0.5): self.wood = wood self.core = core self.length = length self.power = power # 0.0 (weak) to 1.0 (strong) def cast_spell(self, spell, wizard): if spell.is_successful(self, wizard): return spell.effect return None # Explicitly return None (for readability) class Spell: def __init__(self, name, effect, difficulty): self.name = name self.effect = effect self.difficulty = difficulty # 0.0 (easy) to 1.0 (hard) def is_successful(self, wand, wizard): success_rate = ( (1 - self.difficulty) * wand.power * wizard.skill ) return random.random() < success_rate # hogwarts_magic.py import random class Wizard: def __init__(self, name, patronus, birth_year): self.name = name self.patronus = patronus self.birth_year = birth_year self.house = None self.wand = None self.skill = 0.2 # 0.0 (bad) to 1.0 (good) def increase_skill(self, amount): self.skill += amount if self.skill > 1.0: self.skill = 1.0 def assign_wand(self, wand): self.wand = wand print(f"{self.name} has a {self.wand} wand.") def assign_house(self, house): self.house = house house.add_member(self) def cast_spell(self, spell): if self.wand: effect = self.wand.cast_spell(spell, self) if effect: print(f"{self.name} cast {effect}!") else: print(f"{self.name} failed to cast {spell.name}!") else: print(f"{self.name} has no wand!") class House: def __init__(self, name, founder, colours, animal): self.name = name self.founder = founder self.colours = colours self.animal = animal self.members = [] self.points = 0 def add_member(self, member): if member not in self.members: self.members.append(member) def remove_member(self, member): self.members.remove(member) def update_points(self, points): self.points += points def get_house_details(self): return { "name": self.name, "founder": self.founder, "colours": self.colours, "animal": self.animal, "points": self.points } class Wand: def __init__(self, wood, core, length, power=0.5): self.wood = wood self.core = core self.length = length self.power = power # 0.0 (weak) to 1.0 (strong) def cast_spell(self, spell, wizard): if spell.is_successful(self, wizard): return spell.effect return None # Explicitly return None (for readability) class Spell: def __init__(self, name, effect, difficulty): self.name = name self.effect = effect self.difficulty = difficulty # 0.0 (easy) to 1.0 (hard) def is_successful(self, wand, wizard): success_rate = ( (1 - self.difficulty) * wand.power * wizard.skill ) return random.random() < success_rate](https://substackcdn.com/image/fetch/$s_!qaNs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48eab3dd-a08f-40b2-9602-cdbe2642b3cc_1272x4032.png)




![# hogwarts_magic.py # ... class Student(Wizard): def __init__(self, name, patronus, birth_year, school_year): super().__init__(name, patronus, birth_year) self.school_year = school_year self.subject_grades = {} def assign_house_using_sorting_hat(self): # ToDo: Sorting Hat chooses house and assigns it to student. # We'll work on this in the final Year # The parent class has 'assign_house()', which you'll call # in this method ... def take_exam(self, subject, grade): self.subject_grades[subject] = grade def assign_subjects(self, subjects): self.subject_grades = {subject: None for subject in subjects} # ... # hogwarts_magic.py # ... class Student(Wizard): def __init__(self, name, patronus, birth_year, school_year): super().__init__(name, patronus, birth_year) self.school_year = school_year self.subject_grades = {} def assign_house_using_sorting_hat(self): # ToDo: Sorting Hat chooses house and assigns it to student. # We'll work on this in the final Year # The parent class has 'assign_house()', which you'll call # in this method ... def take_exam(self, subject, grade): self.subject_grades[subject] = grade def assign_subjects(self, subjects): self.subject_grades = {subject: None for subject in subjects} # ...](https://substackcdn.com/image/fetch/$s_!6CMv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a9d849-28c6-4aaf-9c68-a2093ac763cd_1326x1176.png)

![# making_magic.py from hogwarts_magic import Wizard, House, Wand, Professor, Student harry = Student("Harry Potter", "stag", 1980, 1) hermione = Student("Hermione Granger", "otter", 1979, 1) gryffindor = House( "Gryffindor", "Godric Gryffindor", ["scarlet", "gold"], "lion", ) slytherin = House( "Slytherin", "Salazar Slytherin", ["green", "silver"], "serpent" ) snape = Professor("Severus Snape", "doe", 1960, "Potions") snape.assign_wand(Wand("Holly", "Phoenix Feather", 10.5)) # Severus Snape has a # <hogwarts_magic.Wand object at 0x7fa915302220> wand. snape.assign_house(slytherin) mcgonagall = Professor( "Minerva McGonagall", "cat", 1935, "Transfiguration" ) mcgonagall.assign_wand(Wand("Hawthorn", "Unicorn Hair", 11)) # Minerva McGonagall has a # <hogwarts_magic.Wand object at 0x7fa9153021c0> wand. mcgonagall.assign_house(gryffindor) harry.assign_subjects(["Potions", "Transfiguration"]) snape.assess_student(harry, 20) # Severus Snape assessed Harry Potter in Potions. The grade is 20%. snape.assess_student(hermione, 60) # Hermione Granger doesn't study Potions. # making_magic.py from hogwarts_magic import Wizard, House, Wand, Professor, Student harry = Student("Harry Potter", "stag", 1980, 1) hermione = Student("Hermione Granger", "otter", 1979, 1) gryffindor = House( "Gryffindor", "Godric Gryffindor", ["scarlet", "gold"], "lion", ) slytherin = House( "Slytherin", "Salazar Slytherin", ["green", "silver"], "serpent" ) snape = Professor("Severus Snape", "doe", 1960, "Potions") snape.assign_wand(Wand("Holly", "Phoenix Feather", 10.5)) # Severus Snape has a # <hogwarts_magic.Wand object at 0x7fa915302220> wand. snape.assign_house(slytherin) mcgonagall = Professor( "Minerva McGonagall", "cat", 1935, "Transfiguration" ) mcgonagall.assign_wand(Wand("Hawthorn", "Unicorn Hair", 11)) # Minerva McGonagall has a # <hogwarts_magic.Wand object at 0x7fa9153021c0> wand. mcgonagall.assign_house(gryffindor) harry.assign_subjects(["Potions", "Transfiguration"]) snape.assess_student(harry, 20) # Severus Snape assessed Harry Potter in Potions. The grade is 20%. snape.assess_student(hermione, 60) # Hermione Granger doesn't study Potions.](https://substackcdn.com/image/fetch/$s_!DWpa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02fadaa1-ab3c-4356-b301-d9a0d38af6fb_1290x1806.png)
![# making_magic.py # ... harry.assign_subjects(["Potions", "Transfiguration"]) print(harry.subject_grades) # {'Potions': None, 'Transfiguration': None} snape.assess_student(harry, 20) # Severus Snape assessed Harry Potter in Potions. The grade is 20%. print(harry.subject_grades) # {'Potions': 20, 'Transfiguration': None} # making_magic.py # ... harry.assign_subjects(["Potions", "Transfiguration"]) print(harry.subject_grades) # {'Potions': None, 'Transfiguration': None} snape.assess_student(harry, 20) # Severus Snape assessed Harry Potter in Potions. The grade is 20%. print(harry.subject_grades) # {'Potions': 20, 'Transfiguration': None}](https://substackcdn.com/image/fetch/$s_!d3sX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a7440cd-63ab-4997-808a-d6ff01e13688_1290x672.png)

Thoroughly enjoyed reading this post. You make it a fun thing to read instead of a cognitive burden.
Fantastic as always Stephen, thank you for sharing and teaching - your posts are great refreshers for those with more experience too