Once you're writing code for other users to use, you run into a simple problem: users generally don't know the module as well as whoever wrote it (you). In this case, you want your users to have a good time and get good use out of your code, so how do you tackle this? There are the obvious ways like 'write good documentation', and 'provide many examples and tutorials' and whilst all these are good answers to the problem there is something a bit more fundamental you can do. You can write your code in such a way that users can't use it wrong.
We've already looked at one of the ways to handle this with private and super-private naming but another way is through the use of 'setters' and 'getters'.
What is a "setter"/"getter"?
A "setter" is the method on a class that "sets" a property. Similarly a "getter" "gets" the property. Typically these are called something like make = car.get_make()
and car.set_make('VW')
. In other languages you, as the user, can't simply edit a class property like you can in Python, so these make a lot more sense. However, what you can do in Python is use the @property
decorator.
How to use @property
Let's consider the HatchBack
example from Part 1, but this time we will call make
make_stored
and create a method called make
with the 'property
decorator applied to it that returns the make_stored
property.
from dataclasses import dataclass
@dataclass
class HatchBack:
make_stored: str
model: str
weight: float
@property
def make(self):
return self.make_stored
car = HatchBack('Skoda', 'Fabia', 1200.)
print(car.make)
This script returns 'Skoda'
but if we know try to set the make
property, by running car.make = 'VW'
, we can't!
In [12]: car.make = 'VW'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[12], line 1
----> 1 car.make = 'VW'
AttributeError: can't set attribute 'make'
The property
decorator effectively turns a method into a 'getter' and blocks the user from setting the property (unless a setter is defined, which we'll get to). A common pattern is to make the property on the class itself private and make the @property
the same but public, which hides the "real" property and makes it look like it can't be changed. Pretty neat!
from dataclasses import dataclass
@dataclass
class HatchBack:
_make: str
model: str
weight: float
@property
def make(self):
return self._make
car = HatchBack('Skoda', 'Fabia', 1200.)
print(car.make)
This also adds a lot of potential to do more interesting things like adding code into the getter
. For example, if your class needs to fetch a lot of data from a server to calculate the value a property takes, you can put that fetching code in the getter. That way, the data is loaded only on demand, i.e. it is only fetched and loaded when the method is called and NOT when the class is instantiated. When you're creating a lot of instances of a class that each contain a lot of data this can be very beneficial.
How to make a setter?
You could just leave it there; perhaps your property doesn't need to be affected by the user. However, if it does then you need to build a setter. This requires a new decorator. This one will be the name of your getter method, dot, "setter", e.g. @make.setter
. It must also have a parameter besides self
for whatever is being set to it. I always call it value
.
from dataclasses import dataclass
@dataclass
class HatchBack:
_make: str
model: str
weight: float
@property
def make(self):
return self._make
@make.setter
def make(self, value: str):
self._make = value
car = HatchBack('Skoda', 'Fabia', 1200.)
car.make = 'VW'
print(car.make)
Once again you can add all sorts of code in here, including type validation as well as any other checks and balances you might find useful.
That's how to build getters and setters into your classes!
For a complete look at the articles in this series, have a look at this overview page: Getting started with classes in Python