Jul 23rd, 2021 - written by Kimserey with .
In Python, we can define variables on the class level or the instance level. In today’s post we will look at the differences in behavior.
Class attribute are set on the class and shared across all instances. Because the attribute is shared between the class or all the instances, changing the variable will change everywhere. Here we defined a
, a class attribute.
1
2
3
4
5
6
7
8
9
10
11
12
13
In [1]: class Test:
...: a = 1
...:
In [2]: Test.a
Out[2]: 1
In [3]: test1 = Test()
In [4]: test2 = Test()
In [5]: test1.a, test2.a
Out[5]: (1, 1)
We can see that all instances of the class have the attributes as well. If the value on the class is reassigned, all instances get the change:
1
2
3
4
5
6
7
In [6]: Test.a
Out[6]: 1
In [7]: Test.a = 10
In [8]: Test.a, test1.a, test2.a
Out[8]: (10, 10, 10)
Reassigning an attribute on the instance level will move the attribute from class to instance level, the class and the other instances are left unchanged.
1
2
3
4
5
6
7
In [9]: Test.a, test1.a, test2.a
Out[9]: (10, 10, 10)
In [10]: test1.a = 20
In [11]: Test.a, test1.a, test2.a
Out[11]: (10, 20, 10)
This is due to the reassignment which created the attribute on the instance level. If we modified the currently assigned variable, the class attribute would have been available on the class and on other instances.
If we use a list as example, we can modify it without reassigning it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [12]: class Test:
...: a = []
...:
In [13]: test1, test2 = Test(), Test()
In [14]: Test.a, test1.a, test2.a
Out[14]: ([], [], [])
In [15]: test2.a.append("hello")
In [16]: Test.a, test1.a, test2.a
Out[16]: (['hello'], ['hello'], ['hello'])
Those behaviors can be seen explicitly by using the __dict__
attribute on the class and on the instances:
1
2
3
4
5
6
7
8
9
10
In [17]: Test.__dict__
Out[17]:
mappingproxy({'__module__': '__main__',
'a': ['hello'],
'__dict__': <attribute '__dict__' of 'Test' objects>,
'__weakref__': <attribute '__weakref__' of 'Test' objects>,
'__doc__': None})
In [18]: test1.__dict__
Out[18]: {}
We can see that the class has the attribute a
, and instance test1
does not have any attribute. Now once we reassign it:
1
2
3
4
5
6
7
In [19]: test1.a = []
In [20]: Test.a, test1.a, test2.a
Out[20]: (['hello'], [], ['hello'])
In [21]: test1.__dict__
Out[21]: {'a': []}
We can see that once we reassign it, the variable is created on the instance level and then take priority over the class attribute. This means that we can also delete it to return to the class attribute:
1
2
3
4
5
6
7
In [22]: test1.a
Out[22]: []
In [23]: del test1.a
In [24]: test1.a
Out[24]: ['hello']
Instance variables are scoped to the instances. So the variables aren’t set on the class level but rather just on the instance level. Just like earlier, we can use __dict__
to see the difference:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [1]: class Test:
...: def __init__(self):
...: self.v = 1
...:
In [2]: Test.__dict__
Out[2]:
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.Test.__init__(self)>,
'__dict__': <attribute '__dict__' of 'Test' objects>,
'__weakref__': <attribute '__weakref__' of 'Test' objects>,
'__doc__': None})
In [3]: t = Test()
In [4]: t.__dict__
Out[4]: {'v': 1}
In [5]: t.v
Out[5]: 1
We can see that v
is only scoped on the instance t
.
In today’s post we saw the difference between class attribute and instance attribute in Python. We saw how we could define them and we saw the change of behavior when the value is reassigned on the instance. This particular behavior is one of those that if not understood can introduce bugs difficult to fix. I hope you liked this post and I see you on the next one!