The Final Year at Hogwarts School of Codecraft and Algorithmancy (Harry Potter OOP Series #7)
Year 7 at Hogwarts School of Codecraft and Algorithmancy • Class methods and static methods
Through the rhythmic clang of the Hogwarts Express, the final-year students huddled together, exchanging looks of eagerness and anxiousness. A new volume lay in wait for their perusal, its regal etchings revealing:
Tales from the Timeless Tome: The Mastery of Static Methods and Class Methods
Their journey in the realm of Python's special methods had opened their eyes to vast perspectives, turning them into true custodians of this majestic language.
As the journey of the train mirrored their journey through Python’s landscapes, a fresh adventure was about to commence. The unveiling of static methods and class methods – a fascinating toolset that tweaked the melody of classes, independent of object instances.
Final-year Python wizards, as the familiar rhythm of the Hogwarts Express carries you ahead, prepare for the grand closing chapter of your Python OOP education. A captivating realm of knowledge and completion awaits.
Students are taller than most professors now–what a change from Year 1! It's the final year at Hogwarts School of Codecraft and Algorithmancy. Graduation is not far off. But there's still some work for the seventh-years.
The Curriculum at Hogwarts (Series Overview)
This is the final post 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: 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 (this article): It's the final year. Students learn about class methods and static methods
Have you been revising throughout the holidays, or do you need a refresher on what you covered in Years 1 to 6? Here's the code in hogwarts_magic.py
so far. You can scroll past it if you're coming here directly from the previous article:
In this final year, you'll complete these classes by adding some class methods and static methods.
The Sorting Hat
You may have noticed similarities and differences between the alternate universes of Hogwarts School of Witchcraft and Wizardry and Hogwarts School of Codecraft and Algorithmancy. One thing that's common to both is the Sorting Hat. And there's still a #ToDo
comment in the function Student.assign_house_using_sorting_hat()
. It's time to deal with this function.
The first flawed attempt
Here's a first attempt at writing assign_house_using_sorting_hat()
. This is not a good solution. You've been warned. But it will highlight a problem that we'll go on to resolve later in this article:
The four houses at Hogwarts are in the list houses
. A house is chosen randomly and assigned to the student. Before you start sending owls with complaints—yes, I know that the real sorting hat doesn't choose the house randomly. But in my Hogwarts, it's my rules!
Can you spot the problem? Let's use this method in making_magic.py
, the script to test your classes, to assign houses to students:
Wow, what a coincidence! They all ended up "randomly" in Gryffindor house, just like the original story! [OK, I'll own up. I commented out the lines with the other houses in assign_house_using_sorting()
to rig the sorting hat's selection. Don't tell anyone.]
But look at the last two print()
calls in this code. First, you print out harry.house.members
. These are all the members in Harry's house. But you only get the object representing Harry back. Isn't Harry's house Gryffindor, the same as Ron and Hermione? Where have the other two gone?
The final line checks whether harry.house
is the same as ron.house
. It's not. The is
keyword checks whether two names refer to the same object. They don't, in this case. Is there some real magic happening here? The students are all assigned to Gryffindor, but they're somehow not in the same house.
Let's look back at Student.assign_house_using_sorting_hat()
. The list houses
is created each time the method is called. But the main problem is that the instances of the four houses are also created each time the method is called. Each time you call this method for a student, a new set of houses is created. Yes, all the Gryffindor houses have the same name, founder, colours, and animal. But they're not the same object. Each student has his or her own house! That defeats the purpose of having houses. They're meant to bring students together, not keep them separate!
The second improved-but-still-imperfect solution
You can't create the house
instances in Student.assign_house_using_sorting_hat()
. So, let's create the houses elsewhere and pass the sequence with the houses to Student.assign_house_using_sorting_hat()
. Here's the updated method:
A list, or any other sequence, is passed to the method, and the house is chosen randomly from there. Now, you need to create the houses somewhere. Let's do this in the script you're using to test your code, making_magic.py
:
The rest of the houses are commented out to rig the sorting hat's choice in this case, since I wanted all students to end up in Gryffindor. You can see all three students when you print out harry.house.members
, and harry.house
and ron.house
are the same object this time.
Third attempt • Class Method
The code you have now works. But we can make it tidier. In a larger project, you'll have several files, and you may need to refer to the houses in several places across different scripts. You want your code to be neater, easier to maintain, and easier to reuse. And you want to ensure you only create them once in a project.
Class attribute
So, let's create a new class called Hogwarts
to deal with anything specific to this school rather than the wizarding world and wizarding schools in general. You can add this class to the existing module or use a new one. I'll use a new file called schools.py
in this article:
There is one attribute in this class so far. But this attribute is different to the ones you used until now. It's not a method. It's an attribute that references data, but it doesn't belong to an instance of the class. It's not attached to an instance using self
. Instead, it belongs to the class itself. It's a class attribute.
You can access a class attribute directly from the class using Hogwarts._houses
. But every instance of the class will also have access to the data, so you can also use Hogwarts()._houses
.
You can test this directly within schools.py
for simplicity, although once you complete testing this class, you can remove all the code that doesn't belong to the class:
Note the difference between Hogwarts._houses
and Hogwarts()._houses
. The first refers to the class, while the second, which includes the parentheses ()
, refers to an instance of the class.
The first two calls to print()
return the same data, and the final print()
confirms they return the same object. The output below is reformatted for clarity:
{
'Gryffindor': <hogwarts_magic.House object at 0x102c87950>,
'Slytherin': <hogwarts_magic.House object at 0x102c87650>,
'Ravenclaw': <hogwarts_magic.House object at 0x102c879d0>,
'Hufflepuff': <hogwarts_magic.House object at 0x102c87a50>,
}
{
'Gryffindor': <hogwarts_magic.House object at 0x102c87950>,
'Slytherin': <hogwarts_magic.House object at 0x102c87650>,
'Ravenclaw': <hogwarts_magic.House object at 0x102c879d0>,
'Hufflepuff': <hogwarts_magic.House object at 0x102c87a50>,
}
True
The same dictionary is displayed twice, and the final call returns True
. This output reminds us that the class House
still doesn't have a __repr__()
special method. But you're a Year 7 student now, so I'll leave this as an exercise for you to do!
Note that I used a leading underscore when defining the name _houses
. This is a convention to indicate this name should only be used internally within the class. It doesn't prevent anyone from using it externally, but it serves as a warning to anyone that it's not intended to be used elsewhere. I broke this convention in the previous examples when I used it directly in the print()
calls outside the class. But we'll fix that now by adding a new method to the class, which fetches one of the houses.
Class method
You can add a method to the class called get_house()
. This method takes the name of a house and returns the House
instance corresponding to the name.
Every method you defined so far had the parameter self
as its first parameter. This parameter refers to the instance of the class used to call the method.
However, you don't need get_house()
to access or modify any data specific to an instance of the class. This method only needs access to the class attribute _houses
. Therefore, instead of creating an instance method, as you've done with other methods so far, you can create a class method using the decorator @classmethod
:
The decorator @classmethod
makes get_house()
a class method rather than an instance method. The convention, in this case, is to use cls
as the first parameter. This parameter refers to the class. Therefore, cls._houses
accesses the class attribute _houses
.
You can now access the instances of House
using the class method get_house()
. And each time you access a house this way, you access the same House
instance and not a new one:
You call the class method get_house()
twice and assign the instance returned to different names. However, these names refer to the same instance, as the output of the print()
call shows:
True
You can go a step further and create two instances of the Hogwarts
class to check whether they return the same House
instances, too:
The output from the two print()
calls shows that, even though the Hogwarts
instances are different, they both return the same Gryffindor House
instances:
Is hogwarts_1 the same instance as hogwarts_2?
False
Is gryffindor_1 the same instance as gryffindor_2?
True
Note that creating separate instances of the Hogwarts
class doesn't match the use case of this class. There should only be one Hogwarts school! You could turn this class into a singleton class—a class that creates only one instance. However, that's a topic for when you graduate from Hogwarts School of Codecraft and Algorithmancy. I'll write about singleton classes another day.
You can add another class method that will be useful soon:
The new class method, get_house_names()
, returns a list with all the house names. Now, you can remove any additional code you used to test this class and its class methods from this script. You should only keep the class definition in schools.py
.
Back in hogwarts_magic.py
, you can update the method Student.assign_house_using_sorting_hat()
to use the new Hogwarts
class and the class method get_house()
:
This method now needs the school as an argument and uses the class methods you have just defined. You can update making_magic.py
to test your code on the instances for Harry, Hermione, and Ron. The outputs are shown as comments in this code block:
Once again, I have "rigged" this to ensure all students end up in Gryffindor on this occasion!
In this article, we're only concerned with Hogwarts. However, you could have other classes similar to Hogwarts
to represent other schools and their houses. You can define a base class for all schools. This base class defines the class methods. Then, can create classes for each school. These classes inherit from the base class. I'll leave this as an exercise for you to do!
International Wand Makers
There's another type of method to discuss before the end of Year 7 to conclude your time at Hogwarts Schools of Codecraft and Algorithmancy.
It is traditional to use inches as a standard unit of length for wands. However, not all wandmakers are comfortable with inches. Some are more comfortable with metric units. Therefore, you can add a method to help them convert from cm to inches.
This method fits best in the Wand
class. However, it doesn't need access to any data from an instance of Wand
. And nor does it need to access any class-level data. You can create a static method. Static methods don't require self
or cls
as parameters:
A static method is a standard function defined within a class definition. It doesn't use data from the instance or the class. You can call the method using the class name, such as Wand.convert_cm_to_inches()
, and you can also call it directly from an instance of the class:
All three calls to the static method convert_cm_to_inches()
are identical since they don't require any information from the instance or class. They all return the same value.
Static methods are often used as utility functions. They don't change the state of an instance, and they don't access data from the class or the instance, but they're part of the class's namespace.
Here's the final version of 'making_magic.py' and 'schools.py'.
Graduation
You've made it. After seven long years, you graduate as a witch or wizard today.
You started learning about the philosophy of the object-oriented programming paradigm in Year 1. Then you learnt how to define classes and add data attributes and instance methods to the class.
In the later years at Hogwarts School of Codecraft and Algorithmancy, the curriculum covered inheritance and special methods. And finally, you dealt with other types of methods in your final year.
Terminology Corner
Class method: A class method is a method that's bound to the class and not an instance of that class. Therefore, it cannot access or modify the state of an instance, but it can access class attributes. By convention, its first parameter is
cls
, which references the class.Static method: A static method is a method that belongs to a class but does not have access to
self
orcls
. Therefore, it doesn't have access to the instance or the class and cannot change the state of an instance.
I focussed more on the practical concepts in this series of articles. There's a fair amount of syntax and terminology that can seem obscure at first. I couldn't avoid it all. But I avoided some of it. So, I'll write about some "long words" in the Series Epilogue: The Missing Terminology, a separate short article.
Of course, as with everything else in life, there's still more to learn out there in the real world about classes and object-oriented programming. Stick around if you want more continuous professional development in your career as a witch or wizard in the world of codecraft and algorithmancy.
Code in this article uses Python 3.11
Stop Stack
#25
Recently published articles on The Python Coding Stack:
Tap, Tap, Tap on The Tiny
turtle
Typewriter. A mini-post usingfunctools.partial()
andlambda
functions to hack keybindings in Python'sturtle
modulePython Quirks? Party Tricks? Peculiarities Revealed… (Paid article) Three "weird" Python behaviours that aren't weird at all
The Mayor of Py Town's Local Experiment: A Global Disaster. Why variables within functions are local
Time for Something Special • Special Methods in Python Classes (Harry Potter OOP Series #6) Year 6 at Hogwarts School of Codecraft and Algorithmancy • Special Methods (aka Dunder Methods)
A Picture is Worth More Than a Thousand Words • Images & 2D Fourier Transforms in Python. For article #20 on The Python Coding Stack, I'm revisiting one of my tutorials from the pre-Substack era
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
A Near-Perfect Picture (Ep. 7). Sampling theory for technical article-writing • Conceptual resolution
The Wrong Picture (Ep. 6). How I messed up • When an analogy doesn't work
The Broom and the Door Frame (Ep. 5). How the brain deals with stories
Mystery in the Manor (Ep. 4). The best story is the one narrated by the technical detail itself
Frame It (Ep. 3). You can't replace the technical content with a story. But you can frame it within a story.
Stats on the Stack
Age: 4 months, 1 week, and 1 day old
Number of articles: 25
Subscribers: 884
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles.