29. Class Attributes#
In our earlier lesson, we got to learn about instance attributes. Here let’s learn about class Attributes 😎
Class Attributes are cousins of Instance Attributes 😬. Just kidding.
As Instance attributes are bound to instances/objects, Class attributes are bound to the Class itself.
Code speaks louder than words 🥁. Let’s get into an example 🙂:
class SuperMarket:
discount = 0 # Here's the class attribute.
def __init__(self, item_price):
self.item_price = item_price
def get_bill(self):
return self.item_price - ((SuperMarket.discount / 100) * self.item_price)
We have created a class SuperMarket
and it contains a variable called discount
, well this is the Class Attribute that we are going to explore.
As we learned that Class Attributes are bound to class whereas Instance Attributes are bound to instance, lets explore it first.
Checking if Instance attribute is bound to Class?
SuperMarket.item_price
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[2], line 1
----> 1 SuperMarket.item_price
AttributeError: type object 'SuperMarket' has no attribute 'item_price'
item_price
is Instance Attribute, We can clearly see that there’s an AttributeError
raised when trying to access instance attribute using the Class rather than the instance.
Checking if Class Attribute is bound to Class?
SuperMarket.discount
0
🎊 Hurray! We are able to access discount
using the Class itself.
Hence, Class Attributes are bound to Class whereas Instance Attributes are bound to Instances.
Well, there’s no reason for you to trust me 😬, So let’s look at the attributes present for the class.
SuperMarket.__dict__
mappingproxy({'__module__': '__main__',
'discount': 0,
'__init__': <function __main__.SuperMarket.__init__(self, item_price)>,
'get_bill': <function __main__.SuperMarket.get_bill(self)>,
'__dict__': <attribute '__dict__' of 'SuperMarket' objects>,
'__weakref__': <attribute '__weakref__' of 'SuperMarket' objects>,
'__doc__': None})
We can see that SuperMarket.__dict__
returned an object which shows the attribute discount
. Hence, discount
is a class attribute as it is bound to its class 😊.
29.1. 🔔 Altering the class Attributes#
First of all, let’s check what is the value of discount?
SuperMarket.discount
0
Let’s create an object of SuperMarket
obj = SuperMarket(100) # Creation of object of the class SuperMarket.
obj.discount
0
We can still see the value of discount
of the obj
to be 0 😊.
Let’s change the discount
class attribute through the class SuperMarket
.
SuperMarket.discount = 10
We have changed the value of the class attribute discount
to 10.
SuperMarket.discount
10
obj.discount
10
We can see that changing the class attribute values changes the values of the attributes of the object as well.
But, what if we change the class attribute via the object? Does it changes the actual Class Attribute? 🤔”
SuperMarket.discount
10
obj.discount
10
At present, we have both SuperMarket.discount
and obj.discount
values to be 10.
obj.discount = 20 # Changing the object's discount value to 20.
obj.discount # Checking if the object's discount changed to 20?
20
SuperMarket.discount # Checking for the class Attribute 'discount' bound to the class.
10
Well, We got to see that when the object’s class attribute value is changed, it doesn’t change the Class’s class attribute value 😬. Well, what’s the reason for this behaviour 🤔?
To identify what paved the way for the above behaviour, let’s create another object of the class SuperMarket
obj2 = SuperMarket(100) # Creating a new object.
Now let’s look at the instance attributes of obj2
by using the special/magic method __dict__
.
obj2.__dict__
{'item_price': 100}
We see that obj2
has only item item_price
. let’s look at obj
also on which we tried to change the value of class attribute discount
.
obj.__dict__
{'item_price': 100, 'discount': 20}
There’s an extra field discount
for obj
, it’s because we tried to change the class attribute’s value which actually shadows/creates a new attribute with the same name as instance attribute
🤔 How to revert the value of discount
to be the class attribute’s value, which in our case should be 10.
Easy Peasy! we just need to delete the attribute of the obj
using del
.
del obj.discount
Now, let’s look at the value of obj.discount
which was earlier 20.
obj.discount
10
Hurray! we are back to square one. By deleting the shadowed discount instance attribute, we are back with the Class Attribute discount
whose value is 10
.
Important
Modifying the Class’s class attribute changes the attribute in all the objects created. But, changing the class attribute bound to the instance/object shadows the class attribute by creating an instance attribute of same name which is bound only to the objectclass.
29.2. Accessing class attributes within the class.#
Well, we got to learn about class attributes, but are they accessible within the class 🤔?
Definitely, they are accessible within the class. Let’s write a new class for the demonstration.
class Counter:
no_of_objects_created = 0
def __init__(self):
Counter.no_of_objects_created += 1
print(
f"Number of objects created of Counter class are: {Counter.no_of_objects_created}"
)
We have created a class called Counter
, its only purpose here is to count the number of objects created out of it. no_of_objects_created
is the class attribute here. Initially it’s value is 0, for every object created of Counter
class, no_of_objects_created
gets increased by 1.
Counter.no_of_objects_created
0
counter_1 = Counter()
counter_2 = Counter()
Number of objects created of Counter class are: 1
Number of objects created of Counter class are: 2
🏆 We did it, we created a class Counter
which counts number of objects created.
In the above example to access the class attribute we used Counter.no_of_objects_created
which is <Class>.<class_attribute>
, There’s another way to access it using self
. Let’s give it a try:
class NewCounter:
no_of_objects_created = 0
def __init__(self):
self.no_of_objects_created += 1
print(
f"Number of objects created of Counter class are: {self.no_of_objects_created}"
)
new_counter_1 = NewCounter()
new_counter_2 = NewCounter()
Number of objects created of Counter class are: 1
Number of objects created of Counter class are: 1
😰 Did it work? No❌, We see that the output is always 1, The culprit here is again Shadowing of class attributes as instance attributes
Note
So should we always use <Class>.<class_attribute>
instead of self.<class_attribute>
🤔? We can use either way while reading the class attribute. But, If we want to alter the class attribute, we should be using <Class>.<class_attribute>
to cascade the change to all the objects.
There’s no advantage of using class attributes if there are chances of altering only for a particular instance using self
, so hard rule is use <Class>.<class_attribute>
for accessing the class within the class 😤.