The next step is to start adding your own methods to your data classes. It often makes sense associate functions that operate on a particular class with that class. And methods on a class have access to all the properties on that class by default.
In Part 1 we looked at the following example to understand the anatomy of a class.
from dataclasses import dataclass
@dataclass
class HatchBack:
make: str
model: str
mass: float
def get_summary(self):
return f'This Hatch back is a {self.make} {self.model} and it weighs {self.mass} kg'
car = HatchBack('Skoda', 'Fabia', 1200.)
summary = car.get_summary()
print(summary)
print(car.make)
Now we can return to it. After Part 1 and 2 you should now understand what each bit of the class is and what it does. The focus of this article is on the method get_summary
.
Methods are functions that are "owned by" the class.
About methods
Methods are owned by the class and must be part of the class definition. The definition must be indented to the same degree as the properties, like in the example above. Class methods typically have to include at least one parameter, the keyword 'self
'. This is a placeholder parameter, and when the code is actually run Python goes behind the scenes and supplies the argument that self accepts: the instance of the class it is being called from. This allows you to access the class instance's properties and methods from within the method. For example
Not all methods need to use the self
parameter because sometimes methods don't need to know about the instance's methods and properties. These are a special type of method known as static methods, and there's a special way of handling them that we'll cover in a later article.
- Repeated notes on
self
from Part 1.- Calling
self
"self" is actually a convention thing and you could technically use any name you like (so long as it's consistent within your class). - However, everyone uses
self
so you may just introduce a lot of unnecessary confusion if you decided to. - If you've ever called a method on an object in the past with an argument when you shouldn't have you may have seen
TypeError: HatchBack.get_summary() takes 1 positional argument but 2 were given
, or a variant, before. This is why!self
counts as a parameter and the argument is automatically supplied by Python at runtime, so if you try to add something in it complains of getting two arguments because Python already gave the method one when we weren't looking.
- Calling
Creating a method
Let's wrap up by putting all the above to use. Let's suppose we are asked by a customer to be able to get the mass of the car in the data class in lbs instead of kg. We draw up a spec.
- The mass must be accessible the same way but with different units
- The units must be specified by the user
- The units can only be SI or Imperial
- SI should be the default
To solve this conundrum we need a new method! We'll call it "get_mass" so that it describes exactly what it's doing. For the units, we are already working in SI units so we just need to create a flag for imperial units. The parameters should be imperial: bool = False
. Then if the user adds True
as a parameter we know they want imperial units, otherwise they'll get their answer in SI units.
Here's what it looks like so far.
from dataclasses import dataclass
@dataclass
class HatchBack:
make: str
model: str
mass: float
def get_summary(self):
return f'This Hatch back is a {self.make} {self.model} and it weighs {self.mass} kg'
def get_mass(self, imperial: bool = False):
# We raise this error to make sure anyone using the method before it's done knows it's
# under construction and doesn't work yet.
raise NotImplementedError
car = HatchBack('Skoda', 'Fabia', 1200.)
print(car.get_mass(imperial=True))
OK, now we have the name and parameters sorted it's time to add the logic. if imperial
is False
we just need to return the mass as is but if it's True
we need to convert to lbs, by multiplying the mass by 2.205. We can access the mass to return it by calling the representation of that instance self
. This is pretty straightforward and the code below shows how I would do it.
from dataclasses import dataclass
@dataclass
class HatchBack:
make: str
model: str
mass: float
def get_summary(self):
return f'This Hatch back is a {self.make} {self.model} and it weighs {self.mass} kg'
def get_mass(self, imperial: bool = False):
if imperial:
return self.mass*2.205
else:
return self.mass
car = HatchBack('Skoda', 'Fabia', 1200.)
print(car.get_mass(imperial=True))
And that's it. If you're a bit confused by the two return
statements in the method, don't worry! Python will always execute the one it gets to first and ignore the other one. If you're still confused I recommend checking out this great RealPython article on the matter: The Python return
Statement.
Part 4: Hashing and Mutability
That concludes Part 3 regarding the basics of classes. Future parts can be considered beyond "basic" level and are more of an intermediate one. Part 4 will cover mutability of data classes.
For a complete look at the articles in this series, have a look at this overview page: Getting started with classes in Python