class Foo(): x = 1
NB: Understanding Class Attributes
This notebook demonstrates how class and instance attributes are related to each other.
A Simple Example
We define a class with one attribute.
We create an instance of the class.
= Foo() foo1
Notice that the class defines the value for the instance.
foo1.x, Foo.x
(1, 1)
Now, the instance attribute changes if the class attribute is changed.
= 2 Foo.x
foo1.x, Foo.x
(2, 2)
What if we the local attribute’s value?
What happens to the class attribute?
= 3 foo1.x
foo1.x, Foo.x
(3, 2)
Turns out we cannot override a global with a local.
We can see that the instance attribute is now unaffected by changing the value of the global.
= 4 Foo.x
foo1.x, Foo.x
(3, 4)
What happened?
By assigning a value to the instance attribute, we converted from global in the class to local in the instance.
This is similar to what we saw with local and global variables in functions.
Finally, notice how changing the value of the class attribute changes all the instance attributes that have not overridden the attribute.
= Foo()
foo2 = Foo() foo3
= 10 Foo.x
foo1.x, foo2.x, foo3.x, Foo.x
(3, 10, 10, 10)
Mutable Class Attributes
There is an interesting gotcha regarding class attributes in Python.
Lists and other mutable data structures can be class attributes and yet have their values modified by instances.
This is kind of weird, and you should look out for it.
To demonstrate, we define a class with two instance variables, one a scalar and one a list.
We define a method to alter the value of each.
We also define a method compare the state of the instance with that of its class.
class MyTest():
# Two class attributes
= 0
foo = []
bar
def add_one(self):
"A method to alter the values of the class attributes."
self.foo += 1
self.bar.append(1)
def replace_bar(self, new_list = []):
"A method to redefined the class list attribute."
self.bar = new_list
def compare_states(self):
"A method to compare the state of instance to that of its class."
print('i.foo =', self.foo)
print('c.foo =', __class__.foo) # Notice how we can refer to an instance's class
print('i.bar =', self.bar)
print('c.bar =', __class__.bar)
Now let’s run some tests.
We define and instance and change nothing.
= MyTest() test1
test1.compare_states()
i.foo = 0
c.foo = 0
i.bar = []
c.bar = []
Now let’s increment the attributes and see the results.
test1.add_one()
test1.compare_states()
i.foo = 1
c.foo = 0
i.bar = [1]
c.bar = [1]
The method does disconnect the instance foo
from the class foo
.
But it does not disconnect the instance bar
from the class bar
.
Instead, a change that took place in one instance affects the state of all other instances!
The difference is that foo
is a scalar, and bar
is a list, i.e. mutable data structure.
We do it again to drive the point home.
test1.add_one()
test1.compare_states()
i.foo = 2
c.foo = 0
i.bar = [1, 1]
c.bar = [1, 1]
Now, let’s replace list itself in the instance.
test1.replace_bar()
for i in range(5):
print("Iter", i)
test1.add_one()
test1.compare_states()print()
Iter 0
i.foo = 3
c.foo = 0
i.bar = [1]
c.bar = [1, 1]
Iter 1
i.foo = 4
c.foo = 0
i.bar = [1, 1]
c.bar = [1, 1]
Iter 2
i.foo = 5
c.foo = 0
i.bar = [1, 1, 1]
c.bar = [1, 1]
Iter 3
i.foo = 6
c.foo = 0
i.bar = [1, 1, 1, 1]
c.bar = [1, 1]
Iter 4
i.foo = 7
c.foo = 0
i.bar = [1, 1, 1, 1, 1]
c.bar = [1, 1]
Notice that now the class list is not altered by the instance list.
It remains in the state before the list itself was re-assigned by the instance.
This is because we redefined the list itself, not just its content.
Let’s define a second instance.
= MyTest() test2
test2.compare_states()
i.foo = 0
c.foo = 0
i.bar = [1, 1]
c.bar = [1, 1]
The new instance has the original value of foo
.
However, notice it starts of with the modified value of bar
before it was replaced.
We do it a few more times to drive the point home.
for i in range(5):
print("Iter", i)
test2.add_one()
test2.compare_states()print()
Iter 0
i.foo = 1
c.foo = 0
i.bar = [1, 1, 1]
c.bar = [1, 1, 1]
Iter 1
i.foo = 2
c.foo = 0
i.bar = [1, 1, 1, 1]
c.bar = [1, 1, 1, 1]
Iter 2
i.foo = 3
c.foo = 0
i.bar = [1, 1, 1, 1, 1]
c.bar = [1, 1, 1, 1, 1]
Iter 3
i.foo = 4
c.foo = 0
i.bar = [1, 1, 1, 1, 1, 1]
c.bar = [1, 1, 1, 1, 1, 1]
Iter 4
i.foo = 5
c.foo = 0
i.bar = [1, 1, 1, 1, 1, 1, 1]
c.bar = [1, 1, 1, 1, 1, 1, 1]
Some take-aways:
- Class attribute changes affect those attributes in all of it instances …
- … unless the instance assigns a value to the attribute.
- However, appending to a list — or, more generally, modifying data in a mutable data structure — does not count as an assignment operation. The instance changes will affect the class state.
- Bottom line: DEFINE CLASS ATTRIBUTES WITH CAUTION.