##  [Getting started with classes in Python Part 3: Methods and properties](/blog/getting-started-classes-python-part-3-methods-and-properties) 

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.

```Python
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`.

&gt; 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.

---

## 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.

```Python
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.

```Python
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](https://realpython.com/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](https://developer.ansys.com/blog/getting-started-classes-python)