Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Adding an Instance Method

00:00 The task was to add an instance method called .drive() that takes some miles as an argument. Then that should increment self.mileage for how many miles you passed in and then test it. So I want to define .drive(), and this instance method won’t be a special method like the first two with the double underscores.

00:27 But this is just a method that is public to the Car class and that I’m going to define now. And still, it takes self as its first argument because I want to do something on the instance of the Car, and then it also takes another argument.

00:42 So I put in the parameter miles in the method definition, and that just means that when calling Car.drive(), you have to pass in miles. That’s going to be a number. And then what do you want to do with this?

00:56 You want to increment self.mileage. So because you’re passing self here and because this is a method on an instance of a Car, you have access to this self object.

01:08 So I can say self.mileage and then just increment it. So I’m going to say += miles. So this is going to take the miles that someone passes when calling .drive() and adds it to self.mileage.

01:24 And you can also do just = self.mileage + miles. That’s the same. But I’m going to go for the shorter version. This is a shortcut to saying .self.mileage + miles.

01:42 I think that should be it. Let’s go ahead and test it. So I will press F5, run it, and go into the interactive console. And then I’m just going to call it car.

01:57 Car() and it will be "blue"

02:01 and have 0 miles. Actually, just for fun, this car is going to be "white", 0 miles. Okay, so we have this Car object. If I print(car), The white car has 0 miles. And now I should be able to say car.drive().

02:20 I’m going to drive this car for a hundred miles, no output. But something happened in here because now if I print(car), it tells me that The white car has 100 miles. So with this instance method here, with .drive(), I accessed an instance attribute, .mileage, and changed it basically with whatever I input here as an argument. So that works.

02:46 I can continue driving the white car, .drive() for, let’s drive it for another a hundred miles, and then it should

02:57 have 200 miles on there. Perfect.

03:02 So good job if you came up with something similar to this, and if you have a different solution, post it in the comments. I’m curious to see.

Avatar image for DimaG

DimaG on Oct. 19, 2023

This is my version of the Car class written using dataclass syntax and operator.methodcaller():

from dataclasses import dataclass
from operator import methodcaller

@dataclass(slots=True, kw_only=True)
class Car:
    """Class representing car."""

    color: str
    mileage: int

    def __str__(self):
        """Returns string representation of car."""
        return f"The {self.color} car has {self.mileage:,} miles."

    def drive(self, miles: int) -> None:
        self.mileage += miles

blue_car = Car(color="blue", mileage=20_000)
red_car = Car(color="red", mileage=30_000)
white_car = Car(color="white", mileage=0)

cars = [blue_car, red_car, white_car]
get_mileage = methodcaller("drive", 100)
get_more_mileage = methodcaller("drive", 100)

for car in cars:
    get_mileage(car)
    print(car)

print()

for car in cars:
    get_more_mileage(car)
    print(car)
Avatar image for Martin Breuss

Martin Breuss RP Team on Oct. 25, 2023

@DimaG thanks for sharing your code using a dataclass and the operator module! :D

Two small notes:

You don’t need get_more_mileage(), it has the same functionality as get_mileage, so you could skip that and just call get_mileage() another time in your second for loop.

Secondly, I’d say that get_mileage() isn’t a very descriptive name for the functionality that you’re encoding here. You’re creating a callable that’ll call .drive() with an argument of 100. Can you come up with a more descriptive name for it?

Nice work expanding on the topics taught in the course! 🙌

Avatar image for Anonymous

Anonymous on Feb. 18, 2024

Here is my version of the challenge:

from random import uniform

class Car:

    def __init__(self,
                 make_car: str,
                 model_car: str,
                 year: int,
                 color: str,
                 mileage: float,
                 ) -> None:
        self.make_car = make_car
        self.model_car = model_car
        self.year = year
        self.color = color
        self.mileage = mileage


    def start(self):
        print(f'Starting {self.make_car} {self.model_car}...')

    def stop(self):
        print(f'{self.model_car} stopped...')

    def drive(self, distance: float) -> None:
        if Car.validate_float(distance, True):
            previous_mileage = self.mileage
            self.mileage += distance
            return (
                f'\tCurrent mileage {previous_mileage:,}km.\n'
                f'\tNew mileage {self.mileage:,}km.'
                )
        else:
            raise ValueError('Incorrect mileage has been given!')

    @staticmethod
    def validate_float(value: float, is_positive: bool=False) -> bool:
        '''if used in the Car Class, validate if distance is not <= 0
           for general use, return false if value is not an instance of type(float)'''
        if isinstance(value, float):
            if is_positive:
                return True if value > 0 else False
            else:
                return True
        else:
            return False


    def __str__(self):
        return f'The {self.color} car has {self.mileage:,}km.'


ford = Car('Ford', 'Ranger', 2014, 'blue', 20_000)
citroen = Car('Citroën', 'Vision', 2030, 'red', 30_000,)

car_obj_mapping = {
    'ford': ford,
    'citroen': citroen,
}
entered_car_brands = set()

for i in range(2):

    while True:
        get_car_brand = input('Car brand: ')
        if get_car_brand in entered_car_brands:
            print(f'{get_car_brand} has already been driven!')
            continue
        elif car_obj_mapping.get(get_car_brand, None):
            entered_car_brands.add(get_car_brand)
            car_obj_mapping[get_car_brand].start()
            break
        else:
            print(f'{get_car_brand} not recognized as a car brand!')

    random_distance = round(uniform(1, 500), 2)

    print(f'{ford.drive(random_distance)}')

    car_obj_mapping[get_car_brand].stop()
else:
    print('Test Drive complete!')
Avatar image for Anonymous

Anonymous on Feb. 18, 2024

Forgot to also change the last line:

# from
print(f'{ford.drive(random_distance)}')

# to
print(f'{car_obj_mapping[get_car_brand].drive(random_distance)}')

Hello,

I have a question, why we are not using the return statement in the .drive() method, although we are using it in the __str__ method?

If I write the drive method like this:

def drive(self, miles):
    return self.mileage += miles

and try to run the code, I will get a syntax error (invalid syntax): “+” sign is marked with red. If I don’t use return then everything is fine.

Avatar image for Martin Breuss

Martin Breuss RP Team on Nov. 18, 2024

@RV there are two separate situations that you’re dealing with here.

1) Returning vs Updating Data Attributes in Classes

You generally don’t need to return a data attribute of a class, when you want to apply some operation on it. Your class is keeping track of the data attribute, and you can always access the updated attribute through e.g. in this case .mileage.

Because you have access to the instance through self, you can directly update the value that .mileage holds through self.mileage.

On the other hand, when you define a .__str__() method, then you’re not updating a data attribute of the object. Instead, you’re creating a new string representation that you need to return if you want to access it outside of the function, e.g. to print it to the console.

2) Returning Values

The second situation, which is why you’re getting the error message, is that the += syntax is an augmented assignment operator, which modifies a variable in place but does not produce a value that Python can return.

So, this is incorrect syntax in Python. You could return the data attribute from the method like so:

def drive(self, miles):
    self.mileage += miles
    return self.mileage

In this case, you use the augmented assignment statement to update self.mileage (effectively writing self.mileage = self.mileage + miles, which is another statement that you couldn’t return), and then you return only the value of self.mileage in the next line.

However, like mentioned in Point 1, you probably don’t want to return self.mileage, because it’s a data attribute on your instance, and you just want to update that attribute.

Hope that makes sense, otherwise keep asking :)

Thank you for your reply, Martin!

Become a Member to join the conversation.