Object-Oriented Programming: Encapsulation in Python

This is the 2nd article in a series on Object-Oriented Programming:


When we are driving a car in real life we don’t actually care or know how it works (unless you are an excellent mechanic).

How it calculates the current speed to show you on the panel, how the pedal connects to rest of the parts to accelerate consuming more gas in a safe way as designed by the engineers and built in a factory.

Bringing all this to our context, you don’t want someone who doesn’t actually know how your car object works to mess around with attributes they shouldn’t.

Moving the wrong parts can cause unexpected behaviors and your program will not execute the way it should, you could even have a car crash!

This is where encapsulation enters.

The right way to modify an attribute in an object, to modify its state, is to use related methods that can safely change the value of the attribute.

In languages like Java or C#, they have special ways to protect attributes that shouldn’t be touched, in Python there is no such feature, but you can use a convention to tell the other programmer they shouldn’t be using that attribute directly.

There are two types of methods and attributes: public and private.

Every public part of an object is accessible and safe to be used, the private ones can only be used inside the object itself.

A private attribute or method has double underscores __.

To access such attributes, you have to use getters and setters.

Private methods are not to be used outside the object.

‘Car’ has 5 attributes: year, model, plate_number, current_speed, and gas.

The ‘Car’ class contains only private attributes.

Notice how use getter and setters for every attribute except for gas and current_speed.

current_speed is always initialized with zero and to increase it you have to use accelerate().

If you increase the speed, there is an if that doesn’t let you accelerate more than 4 at once as a security measure.

If the first test passes then the private method __consume_gas() is called and will check if you have enough gas, if you do, the same amount of speed increased is decreased from self.__gas, if you don’t have enough gas, the car won’t accelerate.
Save the code below in ‘car.py’ and test some variations in the main function.

class Car:
    def __init__(self, year, model, plate_number, gas):
        self.__year = year
        self.__model = model
        self.__plate_number = plate_number
        self.__gas = gas
        self.__current_speed = 0

    def get_year(self):
        return self.__year

    def set_year(self, year):
        self.__year = year

    def get_model(self):
        return self.__model

    def set_model(self, model):
        self.__model = year

    def set_plate_number(self, plate_number):
        self.__plate_number = plate_number

    def get_plate_number(self):
        return self.__plate_number

    def get_gas(self):
        return self.__gas

    def get_current_speed(self):
        return self.__current_speed

    def increase_gas(self, liters):
        self.__gas += value

    def __consume_gas(self, liters):
        if(self.__gas >= liters):
            self.__gas -= liters
            return True
        else:
            return False

    def accelerate(self, value):
        if(value < 5 and self.__consume_gas(value)):
            self.__current_speed += value

    def stop(self):
        self.__current_speed = 0

    def details(self):
        return f'{self.__model}-{self.__year}-{self.__plate_number}'

if __name__ == '__main__':
    car = Car(2018, 'AI5', 'AAA0000', 6)
    car.accelerate(4)
    print(f'Speed: {car.get_current_speed()}-Gas:{car.get_gas()}')
    car.accelerate(3)
    print(f'Speed: {car.get_current_speed()}-Gas:{car.get_gas()}')
    car.accelerate(1)
    print(f'Speed: {car.get_current_speed()}-Gas:{car.get_gas()}')

The output for the test in the main function is:

Speed: 4-Gas:2
Speed: 4-Gas:2
Speed: 5-Gas:1

At first, the car accelerates by 4 since both if tests passed, we are increasing the speed by a number lower than 5 and we have enough gas for it.

Later, when we try to accelerate by 3, we pass the first test, but we only have two liters of gas, so the test doesn’t pass as we remain with the same speed and gas.

Finally, we try to accelerate by 1 and this time both tests passed.

You see, we controlled how our car behaves by avoiding someone from adding too much speed at once or trying to consume more gas than it has.

Nobody can simply set self.__current_speed to 100 because it is a private attribute and we didn’t include a setter for it.

Nobody can just consume gas if not by accelerating the car because __consume_gas(self, liters) is private.

This is how encapsulation shines, you control exactly how the state of your object changes by avoiding collateral damages.