Don't get tripped up by Python class and instance variables
Table of Contents
Instance and class variables #
Let’s start with a simple Python class:
class Data:
system = "QR1"
def __init__(self, value):
self.item = value
Beginner Python developers and especially those coming from other OOP languages like Java or C++ might think that system and value are both instance variables. But they are not. system is a class variable, and item is an instance variable.
In Java or C++ terms, system is a static variable, and item is an instance variable.
To illustrate the difference, let’s create two instances of the Data class:
data1 = Data(10)
data2 = Data(20)
Now, let’s print the memory location of the system and value variables for each instance:
print(data1.item, id(data1.item))
print(data2.item, id(data2.item))
print(data1.system, id(data1.system))
print(data2.system, id(data2.system))
The output will be:
# item variable value and memory location for each instance
10 140732000
20 140732032
# system variable value and memory location for each instance
QR1 140732064
QR1 140732064
Notice that the memory location of system is the same for both instances, while the memory location of value is different for each instance.
Do not access/mutate class variables through instances #
Accessing #
Note that accessing the class variable system through an instance is not recommended. It’s better to access it through the class itself:
print(Data.system)
The same is for changing the class variable through an instance. It’s better to change it through the class itself:
Data.system = "QR2"
print(data1.system, id(data1.system))
print(data2.system, id(data2.system))
print(Data.system, id(Data.system))
Outputs the expected same memory location for system for all instances:
QR2 124387077093680
QR2 124387077093680
QR2 124387077093680
Mutating #
Modifying class variables through instances can cause alot of grief.
Let’s see what happens when we try to modify the class variable through an instance.
data1.system = "DS1"
data2.system = "DS2"
print(data1.system, id(data1.system))
print(data2.system, id(data2.system))
print(Data.system, id(Data.system))
This outputs
DS1 127891514107440
DS2 127891513359536
QR2 127891513483568
Now the memory location of system is different for each instance. As you can imagine, this can cause a lot of confusion and bugs in your code.
The __dict__ attribute #
To understand what’s happening, let’s look at the __dict__ attribute. This attribute is a dictionary that contains all the instance variables of an object. The class itself also has it’s own __dict__ attribute separate from its instances.
Before any changes to the variables, the __dict__ attribute for the Data class and the instances data1 and data2 will be:
print(data1.__dict__)
print(data2.__dict__)
print(Data.__dict__)
Ouputs:
{'item': 10}
{'item': 20}
{'__module__': '__main__', 'system': 'QR1', '__init__': <function Data.__init__ at 0x745adb50ef80>, '__dict__': <attribute '__dict__' of 'Data' objects>, '__weakref__': <attribute '__weakref__' of 'Data' objects>, '__doc__': None}
Notice that the system variable is not present in the instances, but it is present in the class.
Now let’s see what happens after we change the system variable through the instances:
data1.system = "DS1"
data2.system = "DS2"
print(data1.__dict__)
print(data2.__dict__)
print(Data.__dict__)
Outputs:
{'item': 10, 'system': 'DS1'}
{'item': 20, 'system': 'DS2'}
{'__module__': '__main__', 'system': 'QR1', '__init__': <function Data.__init__ at 0x745adb50ef80>, '__dict__': <attribute '__dict__' of 'Data' objects>, '__weakref__': <attribute '__weakref__' of 'Data' objects>, '__doc__': None}
The
__dict__attribute is updated whenever we set variables through the instance
A new system variable is added to the instances as shown in the __dict__ attribute of the instances. The class Data still has the original system variable.
This is a 'feature' of Python, where an attribute can be dynamically added to an object.
So when you access data1.system, Python first looks in the __dict__ attribute of data1 and finds the system variable there. If it doesn’t find it, it looks in the class Data and finds it there.
Prevent modifying class variables through instances #
We now know that setting the system variable through the instance causes a new attribute to be added to the instance __dict__. By overriding the __setattr__ method in the class Data we can prevent this from occuring.
class Data:
system = "QR1"
def __init__(self, value):
self.item = value
def __setattr__(self, name, value):
if name == "system":
raise AttributeError("Cannot modify 'system' attribute")
super().__setattr__(name, value)
Changing the system variable through the Class still works.
data1 = Data(10)
data2 = Data(20)
Data.system = "QR2"
print("data1", data1.system, id(data1.system), data1.__dict__)
print("data2", data2.system, id(data2.system), data2.__dict__)
print("Data", Data.system, id(Data.system), Data.__dict__)
Outputs:
data1 QR2 135190461902768 {'item': 10}
data2 QR2 135190461902768 {'item': 20}
Data QR2 135190461902768 {... 'system': 'QR2' ...}
but trying to change the system variable through the instance raises an error.
data1.system = "DS1"
Raises:
Traceback (most recent call last):
File "/programmerpulse/codesources/python/class_level_variables.py", line 54, in <module>
data1.system = "QR2"
File "/programmerpulse/codesources/python/class_level_variables.py", line 13, in __setattr__
raise AttributeError("Cannot modify 'system' attribute")
AttributeError: Cannot modify 'system' attribute
Conclusion #
Understanding the difference between class and instance variables is crucial in Python. It can save you a lot of headaches and bugs in your code. Remember:
- Class variables are shared among all instances of a class.
- Instance variables are unique to each instance of a class.
- Do not access or modify class variables through instances.
- Use the
__dict__attribute to see the instance variables of an object. - Override the
__setattr__method to prevent modifying class variables through instances.
Read more on using properties in Python classes in the article Where are the Getters and Setters in Python Classes? .