Read-Only Attribute

If you want to make a single attribute read-only on a class, the easiest way to do it is to make a property representing your attribute.

The Problem

Let's say we have a Thing class with value and color attributes:

class Thing:
    def __init__(self, value, color):
        self.value = value
        self.color = color

We want the color to be changeable after the class has been made:

>>> thing = Thing(4, 'red')
>>> thing.color
'red'
>>> thing.color = 'blue'
>>> thing.color
'blue'

But we don't want the value to be changeable (it is right now!).

The Solution

We could try to override the __setattr__ method, which is called whenever any attribute is assigned and then do something special when value is assigned... but that's more complicated than we need for this scenario.

The simplest way to make one attribute immutable is to make a property for that attribute:

class Thing:
    def __init__(self, value, color):
        self._value = value
        self.color = color
    @property
    def value(self):
        return self._value

That property decorator makes it so that whenever we attempt to access the value attribute, that value method we defined will be called and its return value will be given to us:

>>> thing = Thing(4, 'red')
>>> thing.value
4

We've made a getter for this property but not a setter (see the links at the bottom of this page for more on properties) so we won't be able to set this property:

>>> thing = Thing(4, 'red')
>>> thing.value = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

That's exactly the behavior we're looking for! 🎉

Properties don't store data: they call functions

Note that we're not actually storing a value attribute on our Thing class above... we're storing a _value attribute instead. The reason is that if instead of saying self._value = value, we said self.value = value in our initializer, the setter for our property would be called, which doesn't exist. Accessing or assigning to self.value will always go through our property getter/setter now.

We need to actually store our data somewhere and we've chosen to put an _ before the attribute we're storing to note that it's an internal implementation detail of our class which shouldn't be touched from the outside. It still can be changed from the outside (no one's stopping us from doing that) but if you ever see code that reaches into another class and reads or writes to _-prefixed attributes, you should squint at it because something weird is going on. 🤔

>>> thing = Thing(4, 'red')
>>> thing._value = 5
>>> thing.value
5

Related Resources