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
- Another short demo of the
property
decorator - A screencast on properties
- An example of when not to use a property
- More on properties
from Planet Python
via read more
No comments:
Post a Comment