Day 17: Encapsulation

Encapsulation and data hiding in OOP

Encapsulation is the practice of hiding the internal details of an object from the outside world and accessing them only through a well-defined interface. Encapsulation is an important concept in object-oriented programming (OOP) because it helps to prevent the accidental modification of data, simplifies the code, and makes it easier to maintain.

Data hiding is a related concept that involves preventing direct access to an object’s data from outside the object. In other words, data hiding allows you to protect the object’s data by controlling access to it.

In Python, encapsulation and data hiding can be achieved using private variables and methods. Private variables and methods are those that are intended to be used only within the class and not by external code. In Python, you can create private variables and methods by prefixing their names with a double underscore (e.g., __variable_name or __method_name). This convention tells Python to name-mangle the variable or method, which means that it will be renamed to include the name of the class in which it is defined.

Here is an example of encapsulation and data hiding in Python:

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
        
    def display(self):
        print("Name:", self.__name)
        print("Age:", self.__age)
        
    def __increment_age(self):
        self.__age += 1
        
    def celebrate_birthday(self):
        self.__increment_age()
        
person = Person("Alice", 25)
person.display() # Output: Name: Alice, Age: 25
person.__increment_age() # This will give an error, as the method is private
person.celebrate_birthday()
person.display() # Output: Name: Alice, Age: 26

In this example, the Person class has private variables __name and __age, and a private method __increment_age. The display method is a public method that displays the person’s name and age. The celebrate_birthday method is also a public method that increments the person’s age by calling the private method __increment_age. The __increment_age method is private and cannot be accessed directly from outside the class.

When the display method is called, it prints the person’s name and age. When the __increment_age method is called directly, it raises an error because it is a private method. When the celebrate_birthday method is called, it increments the person’s age and calls the private __increment_age method.

This is an example of encapsulation and data hiding in Python. By using private variables and methods, you can hide the internal details of an object and control access to its data. This helps to prevent accidental modification of data and makes the code more robust and maintainable.

Access modifiers in Python (public, protected, and private)

In Python, access modifiers are not enforced in the same way as in some other object-oriented programming languages. However, there are conventions that can be used to indicate the intended access level of class attributes and methods.

In general, attributes and methods that are intended to be used only within the class should be marked as private, while those that are intended to be used outside of the class should be marked as public.

In Python, there is no direct way to create a private attribute or method. Instead, a convention is used where attributes and methods that are intended to be private are prefixed with a double underscore, like this:

class MyClass:
    def __init__(self):
        self.__private_attr = "I am a private attribute"
    
    def __private_method(self):
        print("I am a private method")

Attributes and methods that are intended to be protected can be prefixed with a single underscore. This convention indicates that these attributes and methods should not be used outside of the class, but it is not enforced by the Python interpreter.

class MyClass:
    def __init__(self):
        self._protected_attr = "I am a protected attribute"
    
    def _protected_method(self):
        print("I am a protected method")

Attributes and methods that are not prefixed with an underscore are considered to be public by convention. These attributes and methods can be accessed and used outside of the class.

class MyClass:
    def __init__(self):
        self.public_attr = "I am a public attribute"
    
    def public_method(self):
        print("I am a public method")

It is important to note that these conventions are not enforced by the Python interpreter, and it is still possible to access and modify private and protected attributes and methods from outside the class. However, it is considered bad practice to do so, and it can lead to unexpected behavior in the program.

Abstraction and interface

Abstraction is an important concept in object-oriented programming that allows you to hide the implementation details of a class from its users. The idea is that a user of a class should not need to know how the class works internally, only what it does.

One way to achieve abstraction in Python is through the use of abstract classes and interfaces. An abstract class is a class that cannot be instantiated, but is meant to be subclassed by other classes. It typically contains one or more abstract methods, which are defined but not implemented in the abstract class. A subclass of the abstract class must implement all of the abstract methods before it can be instantiated.

An interface is a collection of abstract methods that define a set of behaviors that a class must implement. In Python, interfaces are not defined as a separate language construct like in Java or C#. Instead, Python programmers typically use abstract classes as interfaces.

Here’s an example of an abstract class in Python:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow"

class Cow(Animal):
    pass

animals = [Dog(), Cat(), Cow()]

for animal in animals:
    print(animal.speak())

In this example, Animal is an abstract class with one abstract method, speak(). The Dog and Cat classes are concrete classes that inherit from Animal and implement the speak() method. The Cow class is also a subclass of Animal, but it does not implement the speak() method, so it remains an abstract class.

The animals list contains instances of the Dog, Cat, and Cow classes. When we iterate over the list and call the speak() method on each animal, the appropriate implementation of the method is called for each animal. The Cow object does not have an implementation of the speak() method, so an error is raised when we try to call it.

Exercise:


Implement a class to model a Car with private attributes and methods for fuel consumption and acceleration