Demystifying Class Variables In Python
Class variables are those variables which are independent of the object they are accessed from. Let’s understand this with an example.
class Animal:
food_ids = [1,2,3]
In this class, I have initialised a variable with a default value. Let’s try to create some instances.
elephant = Animal()
snake = Animal()
As you can see, I have now created two instances of the same class. If I try to access the variable defined by the class from both objects, you can guess what the result would be.
print(elephant.food_ids) # prints [1,2,3]
print(snake.food_ids) # prints [1,2,3]
Well, that wasn’t magic. What happens if I append some value to one of the instance’s accessed variables?
snake.food_ids.append(4)
print(elephant.food_ids) # prints [1,2,3,4]
print(snake.food_ids) # prints [1,2,3,4]
Hmm.. I only appended some value to a variable using one of the instances, but it is reflected in both. There are two forces at play here:
food_ids was initialised with a default value while creating the class; therefore it was a class variable.
A list is a mutable object, meaning that I can change the values inside the list regardless of the initial values in the list.
This means if I use immutable objects like string or integer then this behaviour won’t be the same. If I directly replace the list with another list then also this won’t be replicated. Enough talk, let’s try another example.
snake.food_ids = [4]
print(elephant.food_ids) # prints [1,2,3]
print(snake.food_ids) # prints [4]
Why is this happening? If this was a class variable, then why can’t you replace the variable with an updated value across instances, Python? 😞
Before you start questioning your existence, let's try one other change. Voila!
Animal.food_ids = [4]
print(elephant.food_ids) # prints [4]
print(snake.food_ids) # prints [4]
Wait, what just happened?
Python manages separate dictionary-like objects to track the members/attributes of any instance and class. So, when we access the variable from the instance, Python checks if the variable exists in the instance’s dictionary-like object and if it does not exist then it checks the class’s dict-object.
Now in case, we were directly updating the variable from the class, it works because the original variable in the class is updated. But when we were updating the variable from the instance, Python made a new variable in the instance level with the same name.
For mutable objects like dictionary or list, when we use append or any other native operation of the data-structure, Python looks for the variable in the instance and when it does not find it, it uses the class variable to update the values. Therefore, we see this not-so-intuitive behavior.
Since self also points to the current instance of the class, the same behavior can be expected in the class if use methods to update values. Here's an example.
import random
class Animal:
food_ids = [1,2,3]
def some_method(self):
self.food_ids.append(4) # class variable update
self.food_ids = [random.randint(10,100)] # instance variable update
def some_other_method(self):
print(self.food_ids)
elephant = Animal()
snake = Animal()
elephant.some_method() # sets random value to food_ids
elephant.some_other_method() # prints list with single random value
snake.some_other_method() # prints [1,2,3,4], 4 being updated by elephant
This is really bizarre.