Learn `@classmethod` By Breaking It

ljinLab
4 min readMay 4, 2022
https://pixabay.com/photos/egg-hammer-hit-beat-fragile-583163/#comments

Python is advertised as coming with batteries included. The batteries, built-in modules and features, are incredibly powerful when you are learning python with a specific goal in mind. However, once you get to a certain point in your programming career, you realize that you don’t truly know how these batteries actually work. @classmethod is one of those things. Because I am self-taught, I have no idea how @classmethod decorator works behind the scene, despite how frequently I use it.

While you could look at Python’s source code to learn ins-and-outs, there is a more fun option. Let’s break it.

Basic Usage

Before we mess with the @classmethod decorator, let's first see what it does briefly. When you create an object in Python, one of the first things you learn to do is instantiating it and using the instance of the class. @classmethod allows you to use a method defined in the class without the instantiation part--hence the name, class- method.

Such a feature may not seem all that useful at first, but it comes in handy for numerous tasks. Normal methods (instance methods) need to be instantiated to be used. However, class methods are processed before the instantiation, so anything that needs to be processed before instantiation is often written as a class method. Such a technique is useful for working with Pythonic design patterns (which I plan on writing more about).

LETS BREAK IT

What exactly does the @classmethod do?

To see what exactly it does, let’s not use it. Let’s see what we get when we try to build and use a classmethod without the decorator.

class Person:
def whoami(cls):
return "I am a person!"

if __name__ == "__main__":
print(Person.whoami())

When we run this code, we expectedly get an error. The TypeError says that whoami() is missing 1 required positional argument. However, when we run the code again with the @classmethod decorator, it runs as expected. Therefore, we can assume that the decorator simply fills the first positional parameter by providing the class itself as an argument.

Why are we blindly passing the cls as a first parameter?

When you first learn about class methods, we look around the web and see that people use cls as the first parameter for class methods. So we do as we see; we put cls as the first parameter for class methods. This looks good. We can instinctively see that it is a class despite the fact that it is missing an a and one s. What would happen if we called the parameter something different, like happybirthday?

class Person:
@classmethod
def whoami(happybirthday):
return "I am a person!"

Surprisingly (am I the only one surprised), the code runs as is. This is because values like cls and self are not set in stone by Python. These parameters are named conventionally, so we can call them whatever we want. As long as you refer to them appropriately, the code will still run! Disclaimer: I will not take any responsibility for your PM's or your coworker's actions due to your frivolous naming.

There are articles explaining the differences between cls and self, but what we need to realize is that these are just reference names. It's a typical what's on the inside that counts scenario.

Can we use cls and self at the same time?

The point of this article is to try some dumb stuff and hopefully learn something along the way. One of the dumb things that I wanted to do was to see if I could use cls and self at the same time. I still have no idea why this would be necessary, but it's fun to try stuff.

class Person:
class_msg = "I'm in the class!"

def __init__(self):
self.instance_msg = "I'm in the instance!"

@classmethod
def print_msgs(cls, self):
return f"{cls.class_msg}\n{self.instance_msg}"

if __name__ == "__main__":
print(Person.print_msgs())

Here, we are using both class variable and an instance variable. However, when you run the code, it will not run. This is because we did not provide the instance variable when we told Python that there would be an instance! In order to make it work, we need to create an instance and pass it to the classmethod.

if __name__ == "__main__":
p1 = Person()
print(Person.print_msgs(p1))
# prints
# I'm in the class!
# I'm in the instance!

While this works, if you are ever in this scenario, you may need to rethink your design. This defeats the purpose of having separate method designs. If you are ever working with an instance method (the normal one) and need to use a class variable, you can do so by directly referencing the class. In this case, you could call Person.class_msg to access the class variable. Also, you can do the same thing using a class dunder method like such, self.__class__.cls_msg.

Conclusion

I always had fun breaking things when I was younger. Programming lets me break things without much repercussions, so I plan on breaking some more things later on. By breaking, I learned what the decorator actually does and what the cls actually does. There are more ways to mess with the @classmethod, but we can learn the basics of class methods through these breakages.

There are so many fun things you can do with class methods including building Singletons an an instantiation factory. (These are pieces of the GoF’s design patterns, and I promise that I’ll write more about them in the future.) Don’t be afraid to break things as long as you learn something in the process.

If you see any errors or want to mention something that I have missed, please hit me a tweet while I build (or add Disqus) to my blog.

Originally published at https://www.ljinlabs.com/blog/.

--

--

ljinLab

Programmer, GIS Enthusiast, Entrepreneur, Life long student. (personal website under construction)