DEV Community

Cover image for Python: class attributes, some behaviours we should be aware of.
Be Hai Nguyen
Be Hai Nguyen

Posted on

Python: class attributes, some behaviours we should be aware of.

We look at some behaviours of class attributes which can help to make life easier for us -- mere programmers.

We will look at the following three (3) behaviours:

  1. From the Python official documentation:

    9.4. Random Remarks

    If the same attribute name occurs in both an instance and in a class, then attribute lookup prioritizes the instance.

    https://docs.python.org/3/tutorial/classes.html:
  2. Setting the value of a class attribute via the class will propagate the new value to class instances, whom have not overridden the value of this class attribute. This is in conformance with the documentation above.
  3. Setting the value of a class attribute via the class will propagate the new value down to child classes, but no vice versa.

Let's explore these points via examples.

❶ Attribute lookup prioritises the instance.

This is an example from the documentation page quoted above, I have tweaked it a tiny bit.

class Warehouse:
    purpose = 'Storage'
    region = 'west'
Enter fullscreen mode Exit fullscreen mode

Then:

w1 = Warehouse()
print("1: ", w1.purpose, w1.region)
Enter fullscreen mode Exit fullscreen mode

Output -- these are default class attributes' values:

1:  Storage west
Enter fullscreen mode Exit fullscreen mode
w2 = Warehouse()
w2.region = 'east'
print("2: ", w2.purpose, w2.region)
Enter fullscreen mode Exit fullscreen mode

We just instantiate an instance of Warehouse, then override the value of the region attribute with 'east' (*):

2:  Storage east
Enter fullscreen mode Exit fullscreen mode

-- (*): please note, what I've just written above might not be correct... According to the quoted documentation, the statement w2.region = 'east' might actually mean assigning the new attribute region to instance w2, rather than override as I've written.

❷ Setting the value via class propagates the new value to instances whom have not provided their own value.

We continue with examples in ❶:

Warehouse.region = 'north'
w3 = Warehouse()
print("3: ", w3.purpose, w3.region)
Enter fullscreen mode Exit fullscreen mode

Instance w3 is created with whatever the class attributes' values of Warehouse class:

3:  Storage north
Enter fullscreen mode Exit fullscreen mode

Setting Warehouse.region = 'north', how does this affect the existing two (2) instances w1 and w2?

print(f"4: w1.region: {w1.region}, w2.region: {w2.region}")
Enter fullscreen mode Exit fullscreen mode
4: w1.region: north, w2.region: east
Enter fullscreen mode Exit fullscreen mode

w1 has not set its own value for the region attribute, setting the new value via the class Warehouse does propagate back to instance w1. w2, on the hand, has set its own, so it was not affected.

❸ Setting the value propagates from the parent class to child classes, but not vice versa.

Consider the following classes:

class Engine:
    started = False;

class TwoStrokeEngine(Engine):
    pass

class FourStrokeEngine(Engine):
    pass
Enter fullscreen mode Exit fullscreen mode

In their initial state, started is False for all classes:

print(f"1. Engine.started: {Engine.started}")
print(f"1. TwoStrokeEngine.started: {TwoStrokeEngine.started}")
print(f"1. FourStrokeEngine.started: {FourStrokeEngine.started}\n")
Enter fullscreen mode Exit fullscreen mode
1. Engine.started: False
1. TwoStrokeEngine.started: False
1. FourStrokeEngine.started: False
Enter fullscreen mode Exit fullscreen mode

Let's set Engine.started to True:

Engine.started = True

print(f"2. Engine.started: {Engine.started}")
print(f"2. TwoStrokeEngine.started: {TwoStrokeEngine.started}")
print(f"2. FourStrokeEngine.started: {FourStrokeEngine.started}\n")
Enter fullscreen mode Exit fullscreen mode
2. Engine.started: True
2. TwoStrokeEngine.started: True
2. FourStrokeEngine.started: True
Enter fullscreen mode Exit fullscreen mode

Let's switch Engine.started back to False:

Engine.started = False

print(f"3. Engine.started: {Engine.started}")
print(f"3. TwoStrokeEngine.started: {TwoStrokeEngine.started}")
print(f"3. FourStrokeEngine.started: {FourStrokeEngine.started}\n")
Enter fullscreen mode Exit fullscreen mode
3. Engine.started: False
3. TwoStrokeEngine.started: False
3. FourStrokeEngine.started: False
Enter fullscreen mode Exit fullscreen mode

Let's set FourStrokeEngine.started to True:

FourStrokeEngine.started = True

print(f"4. Engine.started: {Engine.started}")
print(f"4. TwoStrokeEngine.started: {TwoStrokeEngine.started}")
print(f"4. FourStrokeEngine.started: {FourStrokeEngine.started}\n")
Enter fullscreen mode Exit fullscreen mode
4. Engine.started: False
4. TwoStrokeEngine.started: False
4. FourStrokeEngine.started: True
Enter fullscreen mode Exit fullscreen mode

-- We can see that, setting the value propagates from the parent class to child classes, but not vice versa.

What about their instances? Continue on with the examples above:

"""
FourStrokeEngine.started is True from above.
"""

engine = Engine()
two_stroke_engine = TwoStrokeEngine()
four_stroke_engine = FourStrokeEngine()
four_stroke_engine1 = FourStrokeEngine()

print(f"5. engine.started: {engine.started}")
print(f"5. two_stroke_engine.started: {two_stroke_engine.started}")
print(f"5. four_stroke_engine.started: {four_stroke_engine.started}")
print(f"5. four_stroke_engine1.started: {four_stroke_engine1.started}\n")

Engine.started = True

print(f"6. engine.started: {engine.started}")
print(f"6. two_stroke_engine.started: {two_stroke_engine.started}")
print(f"6. four_stroke_engine.started: {four_stroke_engine.started}")
print(f"6. four_stroke_engine1.started: {four_stroke_engine1.started}\n")
Enter fullscreen mode Exit fullscreen mode

Output:

5. engine.started: False
5. two_stroke_engine.started: False
5. four_stroke_engine.started: True
5. four_stroke_engine1.started: True

6. engine.started: True
6. two_stroke_engine.started: True
6. four_stroke_engine.started: True
6. four_stroke_engine1.started: True
Enter fullscreen mode Exit fullscreen mode

Let's set TwoStrokeEngine.started to False, and see what happens to existing instances:

TwoStrokeEngine.started = False

print(f"7. engine.started: {engine.started}")
print(f"7. two_stroke_engine.started: {two_stroke_engine.started}")
print(f"7. four_stroke_engine.started: {four_stroke_engine.started}")
print(f"7. four_stroke_engine1.started: {four_stroke_engine1.started}\n")
Enter fullscreen mode Exit fullscreen mode
7. engine.started: True
7. two_stroke_engine.started: False
7. four_stroke_engine.started: True
7. four_stroke_engine1.started: True
Enter fullscreen mode Exit fullscreen mode

It makes sense that only two_stroke_engine.started was affected.

I did get caught out on some of these issues... And hence this post. I do hope you find this post useful. Thank you for reading and stay safe as always.

Top comments (0)