Python for beginners

Encapsulation

YouTube

Encapsulation is a programming concept that refers to the bundling of data and methods that operate on that data within a single unit, or object. In Python, this concept is implemented through the use of classes.

Encapsulation and Object-Oriented Programming (OOP):

Encapsulation is one of the four main pillars of OOP, alongside abstraction, inheritance, and polymorphism. It emphasises the bundling of data (attributes) and methods (behaviour) that operate on that data within a single unit, which is the class.

Access Modifiers:

Python provides three levels of access control for class members (attributes and methods):

  • Public (Default): Members are accessible from anywhere, both within the class and outside.
  • Protected (Single Underscore Prefix): Members are considered non-public but can still be accessed from outside the class. It’s a convention indicating that the member should not be accessed outside the class.
  • Private (Double Underscore Prefix): Members are considered private and are not accessible from outside the class. Python name mangling is applied to these members, but they can still be accessed with a modified name.

 

class Dog():
    def __init__(self, name, age, breed, weight):
        self.name = name
        self._age = age
        self.breed = breed
        self.__weight = weight
        
dog = Dog('Bobby', 7, 'Labrador', 40)
print(dog.name)
print(dog._age)
print(dog.breed)
print(dog.__weight)

# Outputs:
# Bobby
# 7
# Labrador
# AttributeError: 'Dog' object has no attribute '__weight'
Advantages of Encapsulation:
  • Data Hiding: Encapsulation hides the internal implementation details of a class, preventing direct access to the data from outside the class.
  • Controlled Access: Encapsulation allows you to control how data is accessed and modified. This reduces the risk of accidental or unauthorised changes to the data.
  • Code Organisation: Bundling related data and methods within a class promotes modular and organised code.
  • Flexibility: Internal implementation changes can be made without affecting the external interfaces, as long as the methods’ signatures remain the same.
Encapsulation and Abstraction:

Encapsulation and abstraction often go hand in hand. Encapsulation hides the implementation details, while abstraction hides unnecessary complexities, leading to well-defined, self-contained classes.

Using Encapsulation in Python:

Define class attributes and methods with appropriate access modifiers (public, protected, private).
Encapsulate attributes by making them private (using a double underscore prefix) and providing getter and setter methods to access and modify the data, respectively.
You can use property decorators to create computed attributes that provide controlled access.

Examples of Encapsulation in Python:

Creating a Person class with private attributes like _name, _age, and methods like get_age() and set_age() to ensure controlled access to the attributes.
Implementing a BankAccount class with private balance (_balance) and methods like deposit() and withdraw() that enforce appropriate checks and constraints.

Having a getter and setter alongside your private variables is extremely helpful way of creating a class that forces certain things to be private. If you think back to our vehicle example, you will notice that we can add our own count to each car, this time it increases on every instantiation of the object. This is something that we may not want to be accessible to everyone, because we wouldn’t want someone to maliciously change the Vehicle ID.

from abc import abstractmethod

class Vehicle:
    def __init__(self, model, make, year, weight):
        self.model = model
        self.make = make
        self.year = year
        self.speed = 0
        self.weight = weight
        self._vehicle_ID_number = None
        
    def move(self):
        if self.year > 1970:
            self.speed += 30
            print("You are going 30mph!")

    def get_vehicle_ID(self):
        return self._vehicle_ID_number
            
    @abstractmethod
    def slow_down(self):
        pass

class Car(Vehicle):
    _count = 0
    def __init__(self, model, make, year, weight):
        super().__init__(model, make, year, weight)
        Car._count += 1        
        self._vehicle_ID_number = Car._count
    
    def slow_down(self, weight): 
        if self.speed >= 1:
            self.speed -= 5
            
class Lorry(Vehicle):
    _count = 0
    def __init__(self, model, make, year, weight):
        super().__init__(model, make, year, weight)
        Lorry._count += 1
        self._vehicle_ID_number = Lorry._count
        
    def slow_down(self, weight): 
        if self.speed >= 1:
            if self.weight > 100:
                self.speed -= 1
            elif self.weight > 75:
                self.speed -= 2
            elif self.weight > 50:
                self.speed -= 3
            else: 
                self.speed -= 4
    
car = Car("1 Series", "BMW", 2011, 24)
lorry = Lorry("Big Lorry", "Volkswagen", 2020, 74)
lorry2 = Lorry("Small Lorry", "Volkswagen", 2020, 54)
lorry2.move()
lorry2.slow_down()
print(lorry2.speed)
print(lorry2.get_vehicle_ID())