Encapsulation and Abstraction are key concept in OOP that involve bundling data (attributes) and methods (functions) within a class, controlling access to the internal details.
Encapsulation
Python uses naming conventions to implement encapsulation. By convention, a single underscore (_) prefix indicates a “protected” member and a double underscore (__) prefix indicates a private member of a class.
Note
The only exception to this rule is the dunder/magic methods like __init__, __str__, __repr__, which are methods defined by built-in classes starting and ending with double underscores, which can, in fact, be accessed e.g. in subclasses (e.g. __init__ which can be inherited in a subclass).
“Public” or “private” defines the access scope of class members:
Private: Strictly internal, no direct access
Protected: Intended for internal use, but subclasses can access
Public: Accessible from outside the class
Name Mangling and Private Attributes
Isolation is effectively implemented for private members through a process known as name mangling, which enforces on any attribute that needs to be kept private a strict non-direct access policy. With name-mangling, the Python interpreter replaces any attribute of the form __<name> (at least two leading underscores or at most one trailing underscore) with _classname__<name>. By changing the name of the variable, subclasses are prevented from accidentally overriding its value:
class Account:def__init__(self, owner, balance=0):self.owner = ownerself.__balance = balance # Private attributedef deposit(self, amount):if amount >0:self.__balance += amountprint(f"Added {amount} to the balance")else:print("Deposit amount must be positive")def withdraw(self, amount):if0< amount <=self.__balance:self.__balance -= amountprint(f"Withdrew {amount} from the balance")else:print("Insufficient balance or invalid amount")def get_balance(self):returnf"The balance is {self.__balance}"acc = Account("John")acc.deposit(100)acc.get_balance()
Added 100 to the balance
'The balance is 100'
acc.withdraw(50)acc.get_balance()
Withdrew 50 from the balance
'The balance is 50'
The __balance attribute is encapsulated, making it private and not directly accessible from outside the Account class. The class provides public methods (deposit, withdraw, and get_balance) to interact with the __balance attribute. Trying to access a private attribute directly will raise a AttributeError:
acc.__balance
---------------------------------------------------------------------------AttributeError Traceback (most recent call last)
Cell In[3], line 1----> 1acc.__balanceAttributeError: 'Account' object has no attribute '__balance'
Protected Attributes and Methods
The concept of protected attributes is implemented through a naming convention rather than a language-enforced access restriction, unlike private attributes. They serve as a signal to developers that certain attributes are intended for internal use within the class hierarchy. Their effectiveness depends on developers following the convention i.e. avoid accessing them directly from outside the class.
The goal of abstraction is to define a structure that must be followed by subclasses without specifying the implementation details. In Python can be achieved by using abstract classes and methods from the abc module. Abstract classes are classes that cannot be instantiated and are designed to be referenced by subclasses. They are defined by inheriting the ABC class from the abc module.
Abstract methods are declared in the abstract class by using the @abstractmethod decorator.
The abc Module
In Python, the abc module is used to implement abstract classes.
Shape is an abstract class that defines a contract for its subclasses by specifying the area and perimeter methods as abstract. Rectangle implements these methods, providing the specific logic to calculate the area and perimeter of a rectangle. This exemplifies abstraction by hiding the internal implementation details of calculating areas and perimeters while exposing a consistent interface to the outside world.
Abstraction supports polymorphism by ensuring all shape subclasses will have area() and perimeter() methods: