Class or Instance: Where Do Our Methods and Attributes Belong?
If you've ever worked with an object-oriented programming (OOP) language, or simply dabbled in some of the core concepts of OOP, then you may have come across a key question: does this method / attribute belong to my class or to my instance? Without knowing the answer to this question, you may find yourself dealing with some pesky bugs or unexpected behavior. Let's set things straight once and for all.
For the purposes of this blog, we will show examples of OOP using Python, but much of what we will discuss here relates to OOP as a whole, regardless of which programming language you choose. To start off, let's briefly review some key terminology. Classes refer to blueprints for the objects that we build. Within these classes, we can have a variety of methods and attributes that dictate the characteristics and behaviors of objects from that class. The individual objects that we create from these classes are referred to as instances.
Let's start with a simple example. Consider a baseball manager who wants to track the players on his roster and the total number of homeruns they hit. He could create a Player class and initialize Player objects with a name and current homerun total:
class Player:
def __init__(self, name, homeruns):
self.name = name
self.homeruns = homeruns
charlie = Player('Charlie', 20)
jacob = Player('Jacob', 28)
charlie.homeruns
# 20
jacob.homeruns
# 28
Here, we have defined a Player class and two Player instances. Each of these instances has their own attributes of name and homeruns, which were assigned at the time that each object was created. We could give some additional functionality by adding an instance method to increment the total homeruns:
class Player:
def __init__(self, name, homeruns):
self.name = name
self.homeruns = homeruns
def add_homerun(self):
self.homeruns += 1
charlie = Player('Charlie', 20)
jacob = Player('Jacob', 28)
charlie.homeruns
# 20
charlie.add_homerun()
charlie.homeruns
# 21
jacob.homeruns
# 28
In our code above, we have now established characteristics and behavior for our instances. Whenever we create a new object of the Player class, the attributes and functionality are specific to that individual instance. Note that in the above example, we added a homerun to Charlie's homerun total, but Jacob's homerun total remains unchanged. This is because the "add_homerun" method belongs to a particular instance, NOT to the entire class.
At this point, you may be asking yourself, why would we even care about the whole class? Well, there are in fact many scenarios when we might want to know about the entire class. What if, for example, our baseball manager wants to know how many players he has on his team? Or what if he wants some summary statistics related to the homeruns of all his players? It would be easy to calculate those answers as our code stands right now since we only have two players, but think about what happens as we grow our application and have more player instances. We need some way to track all the instances of players that exist. If only there were some way to do this...
You guessed it! This is where class attributes and class methods become useful. One of the most common use cases for a class attribute is to create a list that we continuously update any time a new object is created. Let's take a look:
class Player:
all = []
def __init__(self, name, homeruns):
self.name = name
self.homeruns = homeruns
Player.all.append(self)
def add_homerun(self):
self.homeruns += 1
charlie = Player('Charlie', 20)
jacob = Player('Jacob', 28)
[player.name for player in Player.all]
# ['Charlie', 'Jacob']
There are a few different steps going on here. First, we have created a new class variable, "all," which starts out as an empty list. Because this variable is not defined within any of the methods in our class, it belongs to the class, which is an object itself. As such, if we want to access this variable, we can do so using dot notation on the Player class (i.e. "Player.all"). Note that "Player.all" will contain player objects as its elements, so we can use list comprehension to extract the individual attributes (in this case, the names) of each player that has been created.
We can also create class methods, which allow us to define certain behavior for the entire class. If our manager wants to be able to calculate the average number of homeruns amongst all of his players, we can create an "average_homeruns" method:
class Player:
all = []
def __init__(self, name, homeruns):
self.name = name
self.homeruns = homeruns
Player.all.append(self)
def add_homerun(self):
self.homeruns += 1
@classmethod
def average_homeruns(cls):
homeruns = [player.homeruns for player in cls.all]
return sum(homeruns) / len(homeruns)
charlie = Player('Charlie', 20)
jacob = Player('Jacob', 28)
Player.average_homeruns()
# 24.0
Once again, we have a few different things going on here. First, you'll notice we have used the classmethod decorator, which allows us to define a method for an entire class, rather than just an instance of a class. In order to use this decorator, we must pass in the "cls" keyword, rather than the "self" keyword that we typically use for instance methods.
Next, we access the "all" attribute of the class using the cls keyword and use list comprehension to extract the homeruns for each player on the team. We can then use this list to calculate the average using sum and length methods on our homeruns list. Lastly, we call the average_homeruns method directly on our Player class and can see the average homeruns of everyone on the team.
Understanding the difference between class attributes / methods and instance attributes / methods is very important. If you are ever in doubt, ask yourself one question: does this method or attribute belong to an individual object or to an entire class? In the example above, it makes sense that our average_homeruns method is held in the class, as an individual player instance should solely be responsible for holding the name and homeruns of one player. The Player class, conversely, should be responsible for summary information about all of the Player instances. Be sure to keep these principles in mind as you delve deeper into OOP!
Subscribe to my newsletter
Read articles from Taylor Ng directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by