Applying Polymorphism, Encapsulation, and Abstraction in Python
Course: Python Programming Training
Topic: Applying Polymorphism, Encapsulation, and Abstraction
Course Link: Python Training Course at JustAcademy
Demo Link: Register for a Course Demo
Website: JustAcademy Homepage
1. Introduction to the Three Core OOP Principles
Object-oriented programming (OOP) is a programming approach that organizes software around classes and objects. A class defines attributes and methods, while an object represents a particular instance of that class.
Python supports several OOP principles that help developers build reusable, maintainable, and extensible applications. Three important principles are polymorphism, encapsulation, and abstraction.
- Polymorphism: Allows different objects to respond to the same operation or method call in different ways.
- Encapsulation: Combines data and related behavior within a class and provides controlled ways to access or modify that data.
- Abstraction: Exposes essential functionality while hiding unnecessary implementation details behind a clear interface.
These principles are often used together. For example, a payment application may use abstraction to define a payment interface, polymorphism to support different payment methods, and encapsulation to manage each payment method's internal data.
2. Learning Objectives
After completing this topic, you will be able to:
- Understand polymorphism, encapsulation, and abstraction.
- Implement polymorphism using method overriding and duck typing.
- Understand method overloading alternatives in Python.
- Protect and manage attributes using encapsulation techniques.
- Use properties to control attribute access.
- Define abstract classes and abstract methods using the
abc module.
- Apply all three principles in practical Python programs.
- Identify common implementation mistakes and follow OOP best practices.
3. Understanding Polymorphism in Python
3.1 What Is Polymorphism?
The word polymorphism means "many forms." In programming, polymorphism allows the same interface, method name, or operation to behave differently depending on the object or data involved.
For example, a make_sound() method might produce a different sound for a dog and a cat. The calling code can use the same method name without needing to know every implementation detail of each animal.
3.2 Simple Example of Polymorphism
class Dog:
def make_sound(self):
return "Bark"
class Cat:
def make_sound(self):
return "Meow"
dog = Dog()
cat = Cat()
print(dog.make_sound())
print(cat.make_sound())
Output:
Bark
Meow
Explanation:
- Both classes define a method named
make_sound().
- The method produces different results depending on the object.
- The same method call pattern works with both objects.
3.3 Polymorphism Through a Common Function
A function can operate on different objects as long as they provide the expected behavior.
class Dog:
def make_sound(self):
print("Dog barks")
class Cat:
def make_sound(self):
print("Cat meows")
class Cow:
def make_sound(self):
print("Cow moos")
def animal_sound(animal):
animal.make_sound()
animals = [Dog(), Cat(), Cow()]
for animal in animals:
animal_sound(animal)
Output:
Dog barks
Cat meows
Cow moos
The animal_sound() function does not need separate branches for each class. It simply calls the method that each object provides.
3.4 Method Overriding
Method overriding occurs when a subclass defines a method with the same name as a method inherited from its parent class. The subclass implementation is used when that method is called on an instance of the subclass.
class Vehicle:
def start(self):
print("Vehicle is starting")
class Car(Vehicle):
def start(self):
print("Car engine is starting")
class Motorcycle(Vehicle):
def start(self):
print("Motorcycle engine is starting")
vehicle = Vehicle()
car = Car()
motorcycle = Motorcycle()
vehicle.start()
car.start()
motorcycle.start()
Output:
Vehicle is starting
Car engine is starting
Motorcycle engine is starting
Key points:
Car and Motorcycle inherit from Vehicle.
- Both subclasses override the inherited
start() method.
- Python selects the implementation based on the object's class.
3.5 Using super() with Method Overriding
The super() function allows a subclass to call an implementation defined higher in the method resolution order. This is useful when the subclass wants to extend rather than completely replace the parent's behavior.
class Vehicle:
def start(self):
print("Checking vehicle system")
class Car(Vehicle):
def start(self):
super().start()
print("Car engine is starting")
car = Car()
car.start()
Output:
Checking vehicle system
Car engine is starting
The subclass first reuses the parent's behavior and then adds its own behavior.
3.6 Polymorphism Through Duck Typing
Python supports duck typing, a programming style in which an object is used according to the operations it supports rather than requiring it to belong to a particular inheritance hierarchy.
The idea is often summarized as: if an object provides the required behavior, it can be used for that operation.
class PDFReport:
def export(self):
return "Exporting PDF report"
class ExcelReport:
def export(self):
return "Exporting Excel report"
class CSVReport:
def export(self):
return "Exporting CSV report"
def export_report(report):
print(report.export())
export_report(PDFReport())
export_report(ExcelReport())
export_report(CSVReport())
Output:
Exporting PDF report
Exporting Excel report
Exporting CSV report
The report classes do not need to share a parent class for this example to work. Each object only needs to provide a compatible export() method.
3.7 Polymorphism with Built-in Functions
Python's built-in functions often work with different data types through a shared interface.
print(len("Python"))
print(len([10, 20, 30]))
print(len({"name": "Asha", "age": 25}))
Output:
6
3
2
The len() function works with strings, lists, and dictionaries, although the meaning of length depends on the object.
3.8 Method Overloading Alternatives in Python
In languages such as Java, method overloading commonly means defining multiple methods with the same name but different parameter lists. Python does not support traditional signature-based method overloading in the same way. Defining a second method with the same name in a class replaces the earlier definition in the class namespace.
Python programmers can often achieve similar flexibility through default parameters, variable-length arguments, or type-based dispatch.
Example using default parameters:
class Calculator:
def add(self, a, b=0, c=0):
return a + b + c
calculator = Calculator()
print(calculator.add(5))
print(calculator.add(5, 10))
print(calculator.add(5, 10, 15))
Output:
5
15
30
Example using variable-length arguments:
class Calculator:
def add(self, *numbers):
return sum(numbers)
calculator = Calculator()
print(calculator.add(2, 3))
print(calculator.add(2, 3, 4, 5))
Output:
5
14
3.9 Benefits of Polymorphism
- Reduces repetitive conditional logic.
- Makes it easier to add new implementations.
- Encourages reusable functions and interfaces.
- Supports extensible application design.
- Allows different objects to work with the same calling code.
4. Understanding Encapsulation in Python
4.1 What Is Encapsulation?
Encapsulation combines data and the methods that operate on that data inside a class. It also encourages controlled access to an object's internal state so that data remains valid and consistent.
For example, a bank account should not allow an arbitrary invalid withdrawal that makes the balance negative when the application's rules prohibit overdrafts. A class can expose a withdrawal method that checks the amount before updating the balance.
4.2 Public Attributes
Public attributes can be accessed directly from outside the class. They are suitable when unrestricted access is appropriate.
class Student:
def __init__(self, name):
self.name = name
student = Student("Rohan")
print(student.name)
student.name = "Karan"
print(student.name)
Output:
Rohan
Karan
The name attribute is public, so the caller can read or replace its value directly.
4.3 Protected Attributes by Convention
A single leading underscore, such as _balance, signals that an attribute is intended for internal use or use by subclasses. This is a convention, not strict access control. Python does not prevent external code from accessing such an attribute.
class Employee:
def __init__(self, name, salary):
self.name = name
self._salary = salary
employee = Employee("Priya", 50000)
print(employee.name)
print(employee._salary)
Output:
Priya
50000
Although the example can access _salary, the underscore communicates that callers should generally use a public interface instead of depending directly on the internal attribute.
4.4 Private-Like Attributes and Name Mangling
A double leading underscore, such as __balance, triggers name mangling in most ordinary class definitions. Python changes the attribute's internal name to include the class name, which helps avoid accidental name conflicts in subclasses.
This mechanism is not a security boundary and does not make data absolutely inaccessible.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
account = BankAccount(5000)
print(account.get_balance())
Output:
5000
Directly accessing account.__balance normally raises an AttributeError because the stored attribute is name-mangled.
4.5 Controlled Access Through Methods
Encapsulation allows a class to expose methods that validate requests before changing internal data.
class BankAccount:
def __init__(self, owner, balance=0):
if balance < 0:
raise ValueError("Initial balance cannot be negative")
self.owner = owner
self.__balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.__balance += amount
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self.__balance:
raise ValueError("Insufficient balance")
self.__balance -= amount
def get_balance(self):
return self.__balance
account = BankAccount("Aarav", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance())
Output:
1300
Explanation:
- The balance is stored in the name-mangled attribute
__balance.
- The
deposit() method rejects non-positive amounts.
- The
withdraw() method checks both the amount and available balance.
- The
get_balance() method provides a controlled way to read the balance.
This example illustrates encapsulation. In real financial software, additional requirements such as decimal-based currency calculations, transaction logging, concurrency controls, and authorization would also be necessary.
4.6 Encapsulation with Properties
Python's @property decorator lets a class expose a method as an attribute. This can provide a convenient interface while keeping validation logic inside the class.
class Product:
def __init__(self, price):
self.price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self.__price = value
product = Product(250)
print(product.price)
product.price = 300
print(product.price)
Output:
250
300
How it works:
@property defines the getter for price.
@price.setter defines the setter for the same property.
- When a value is assigned to
product.price, the setter validates it.
- The actual value is stored in
__price, avoiding recursive calls to the property.
If a negative value is assigned, the setter raises a ValueError.
4.7 Read-Only Properties
A property without a setter can be used as a read-only attribute through the normal public interface.
class Circle:
def __init__(self, radius):
if radius <= 0:
raise ValueError("Radius must be positive")
self._radius = radius
@property
def radius(self):
return self._radius
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.radius)
print(circle.area)
Output:
5
78.53975
The caller can read radius and area through properties, but assigning to circle.area raises an AttributeError unless a setter is defined. Python's object model is not designed to provide absolute protection against deliberate modification of internal attributes.
4.8 Benefits of Encapsulation
- Protects object state from accidental invalid updates.
- Centralizes validation logic.
- Separates the public interface from internal implementation.
- Makes future implementation changes easier.
- Improves code maintainability and readability.
- Helps enforce business rules consistently.
5. Understanding Abstraction in Python
5.1 What Is Abstraction?
Abstraction means exposing essential functionality while hiding implementation details that callers do not need to understand.
For example, a person can drive a car by using its steering wheel, accelerator, and brakes without needing to understand every detail of the engine's internal operation. The controls form an interface, while the internal mechanisms implement the behavior.
In software, abstraction helps define what an object must do without requiring every implementation to work in exactly the same way internally.
5.2 Abstraction Using Abstract Classes
Python provides the abc module, which supports abstract base classes. An abstract base class can define abstract methods that concrete subclasses must implement before they can be instantiated.
The @abstractmethod decorator marks a method as abstract. A class with unresolved abstract methods cannot normally be instantiated.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rectangle = Rectangle(5, 4)
print(rectangle.area())
Output:
20
Explanation:
ABC is the base class used to define an abstract base class.
Shape specifies that concrete shapes must provide an area() implementation.
Rectangle implements the required method.
- The program can create a rectangle and calculate its area.
5.3 What Happens If an Abstract Method Is Not Implemented?
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
pass
rectangle = Rectangle()
Result: Python raises a TypeError because Rectangle has not implemented the abstract method area().
This behavior helps catch incomplete implementations before objects are used.
5.4 Abstract Classes with Multiple Abstract Methods
An abstract class can define multiple required operations. A concrete subclass must implement all inherited abstract methods before it can be instantiated.
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
pass
@abstractmethod
def refund(self, amount):
pass
class CardPayment(PaymentMethod):
def pay(self, amount):
return f"Paid {amount} using a card"
def refund(self, amount):
return f"Refunded {amount} to the card"
payment = CardPayment()
print(payment.pay(1000))
print(payment.refund(200))
Output:
Paid 1000 using a card
Refunded 200 to the card
The abstract base class specifies the required interface, while CardPayment supplies the concrete behavior.
5.5 Abstract Methods Can Contain Shared Implementation
An abstract method can contain executable code rather than only pass. A subclass may call that shared implementation through super() while providing its own additional behavior.
from abc import ABC, abstractmethod
class Notification(ABC):
@abstractmethod
def send(self, message):
print(f"Preparing notification: {message}")
class EmailNotification(Notification):
def send(self, message):
super().send(message)
print(f"Sending email: {message}")
notification = EmailNotification()
notification.send("Welcome")
Output:
Preparing notification: Welcome
Sending email: Welcome
5.6 Abstraction Without Abstract Base Classes
Abstraction is a design principle, not a feature limited to abstract classes. A well-designed class can provide a simple public interface while keeping internal steps inside private or internal methods.
class CoffeeMachine:
def make_coffee(self):
self.__boil_water()
self.__brew_coffee()
return "Coffee is ready"
def __boil_water(self):
print("Boiling water")
def __brew_coffee(self):
print("Brewing coffee")
machine = CoffeeMachine()
print(machine.make_coffee())
Output:
Boiling water
Brewing coffee
Coffee is ready
The user calls the single public method make_coffee() instead of coordinating the internal steps directly.
5.7 Benefits of Abstraction
- Reduces complexity for callers.
- Defines consistent interfaces for related classes.
- Encourages interchangeable implementations.
- Improves maintainability by separating interface and implementation.
- Helps teams work independently against agreed interfaces.
- Can make unit testing easier by allowing alternative implementations.
6. Differences Between Polymorphism, Encapsulation, and Abstraction
| Feature |
Polymorphism |
Encapsulation |
Abstraction |
| Main purpose |
Allow different objects to provide different behavior through a common operation. |
Combine data and behavior and control how internal state is accessed. |
Expose essential behavior while hiding unnecessary implementation details. |
| Focus |
Flexible behavior and interchangeable implementations. |
Data integrity and controlled access. |
Interfaces and reduced complexity. |
| Common Python techniques |
Method overriding, duck typing, shared interfaces. |
Methods, naming conventions, properties, validation. |
Abstract base classes, abstract methods, public interfaces. |
| Typical example |
Different payment classes implement pay(). |
A bank account validates deposits and withdrawals. |
A payment interface defines operations without specifying every implementation. |
| Key benefit |
Extensibility and code reuse. |
Reliable and maintainable object state. |
Simpler usage and clear contracts. |
| Question it answers |
How can the same operation work differently for different objects? |
How should data and its updates be managed? |
What should an object expose, and what can remain hidden? |
7. Applying All Three Principles in One Project
7.1 Project Overview: Payment Processing System
A payment processing system is a useful example because different payment methods share a common purpose but have different implementations. The system also needs controlled data and a clear interface.
This example demonstrates:
- Abstraction: An abstract
PaymentMethod class defines the required pay() method.
- Encapsulation: Each payment object stores its transaction total internally and validates transaction amounts.
- Polymorphism: Card and wallet payment classes implement the same method differently.
7.2 Complete Python Implementation
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
def __init__(self):
self.__total_paid = 0.0
@property
def total_paid(self):
return self.__total_paid
def _record_payment(self, amount):
if not isinstance(amount, (int, float)) or isinstance(amount, bool):
raise TypeError("Amount must be numeric")
if amount <= 0:
raise ValueError("Amount must be positive")
self.__total_paid += amount
@abstractmethod
def pay(self, amount):
pass
class CardPayment(PaymentMethod):
def pay(self, amount):
self._record_payment(amount)
return f"Processing card payment of {amount:.2f}"
class WalletPayment(PaymentMethod):
def pay(self, amount):
self._record_payment(amount)
return f"Processing wallet payment of {amount:.2f}"
def process_payment(payment_method, amount):
print(payment_method.pay(amount))
card = CardPayment()
wallet = WalletPayment()
process_payment(card, 1500)
process_payment(wallet, 500)
print(f"Card total: {card.total_paid:.2f}")
print(f"Wallet total: {wallet.total_paid:.2f}")
Output:
Processing card payment of 1500.00
Processing wallet payment of 500.00
Card total: 1500.00
Wallet total: 500.00
7.3 Detailed Explanation
Abstraction: The abstract PaymentMethod class requires concrete payment classes to implement pay(). It defines the interface without specifying the exact payment process.
Encapsulation: The total paid amount is stored in __total_paid. The public total_paid property provides read access, while _record_payment() validates the amount before updating the total.
Polymorphism: The process_payment() function accepts different payment objects and calls pay(). The correct implementation runs according to the object's class.
Design note: This is a learning example, not production payment software. Real payment processing requires secure payment-provider integrations, appropriate currency handling, authentication, error management, and transaction reconciliation.
8. Another Practical Example: Employee Management
The following example demonstrates encapsulation and polymorphism while using an abstract class to define the behavior expected from employee types.
from abc import ABC, abstractmethod
class Employee(ABC):
def __init__(self, name, base_salary):
if base_salary < 0:
raise ValueError("Salary cannot be negative")
self.name = name
self.__base_salary = base_salary
@property
def base_salary(self):
return self.__base_salary
@abstractmethod
def calculate_salary(self):
pass
class FullTimeEmployee(Employee):
def calculate_salary(self):
return self.base_salary
class CommissionEmployee(Employee):
def __init__(self, name, base_salary, commission):
super().__init__(name, base_salary)
if commission < 0:
raise ValueError("Commission cannot be negative")
self.commission = commission
def calculate_salary(self):
return self.base_salary + self.commission
employees = [
FullTimeEmployee("Asha", 50000),
CommissionEmployee("Rohan", 30000, 8000)
]
for employee in employees:
print(employee.name, employee.calculate_salary())
Output:
Asha 50000
Rohan 38000
Here, the abstract Employee class defines the salary-calculation contract. The base salary is managed through a property, and the subclasses provide different salary-calculation behavior.
9. Common Mistakes and Troubleshooting
| Problem |
Why It Happens |
How to Fix It |
| Subclass does not behave as expected |
The subclass may not override the intended method, or the wrong object is being used. |
Check the method name, inheritance, and object type. |
| Abstract class cannot be instantiated |
One or more abstract methods have not been implemented. |
Implement all required abstract methods in the concrete subclass. |
| Invalid values modify internal data |
Validation is missing from the method that updates the state. |
Validate input before changing the stored value. |
| Double underscore attribute is inaccessible |
Python applies name mangling to attributes with double leading underscores. |
Use a public method or property to access the data. |
| Assuming underscore attributes are completely private |
A single leading underscore is only a convention, and double-underscore name mangling is not a security feature. |
Use clear public interfaces and avoid relying on internal attributes from outside the class. |
| Same method name defined twice |
The later definition replaces the earlier definition in the class namespace. |
Use default parameters, variable-length arguments, or a deliberate dispatch strategy. |
| Unrelated objects cause AttributeError |
A function assumes an object has a method or attribute that it does not provide. |
Document the expected interface, use abstract base classes where appropriate, or validate inputs. |
| Property setter calls itself recursively |
The setter assigns to the same property instead of an internal storage attribute. |
Store the value in a separate attribute such as self._price or self.__price. |
9.1 Common Mistake: Incorrect Property Setter
class Product:
@property
def price(self):
return self.price
@price.setter
def price(self, value):
self.price = value
This code recursively calls the property getter or setter because both methods access self.price internally.
Corrected version:
class Product:
def __init__(self, price):
self.price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self.__price = value
9.2 Common Mistake: Instantiating an Abstract Class
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def generate(self):
pass
report = Report()
Python raises a TypeError because Report has an abstract method that has not been implemented.
Corrected version:
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def generate(self):
pass
class SalesReport(Report):
def generate(self):
return "Sales report generated"
report = SalesReport()
print(report.generate())
Output:
Sales report generated
10. Best Practices for Applying OOP Principles
- Give each class a clear responsibility.
- Use polymorphism when multiple implementations should support the same operation.
- Prefer small, focused methods over large methods with many type checks.
- Use encapsulation to keep object state valid and centralize important validation rules.
- Remember that Python's underscore conventions and name mangling are not security mechanisms.
- Use properties when attribute access needs validation or computed behavior.
- Use abstract base classes when a shared contract needs to be explicitly enforced.
- Avoid creating unnecessary abstract classes for simple operations.
- Use
super() to reuse parent behavior when appropriate.
- Write tests for valid inputs, invalid inputs, boundary values, and subclass behavior.
- Use meaningful names such as
calculate_salary(), process_payment(), and validate_amount().
- Document the expected interface when functions accept different object types through duck typing.
- Keep sensitive information, credentials, and authorization checks out of simplistic example-level attribute protection mechanisms.
11. Practice Exercises
Exercise 1: Polymorphism
Create Circle, Rectangle, and Triangle classes. Each class should implement an area() method. Store the objects in a list and call area() for each object using the same loop.
Exercise 2: Method Overriding
Create a base class named Notification with a method called send(). Create subclasses for email, SMS, and push notifications that override the method with different behavior.
Exercise 3: Encapsulation
Create a BankAccount class with a name-mangled balance attribute. Add methods for deposits and withdrawals. Reject zero or negative transaction amounts and prevent withdrawals that exceed the available balance.
Exercise 4: Properties
Create a Product class with a price property. Ensure that negative prices cannot be assigned. Add a second property that calculates a discounted price.
Exercise 5: Abstraction
Create an abstract class named Storage with abstract methods save() and load(). Implement subclasses such as FileStorage and MemoryStorage that provide their own behavior.
Exercise 6: Combine All Three Principles
Build a library management application with the following requirements:
- An abstract base class defining required library-item operations.
- Different item classes that implement the same operation differently.
- Encapsulated availability or borrowing state.
- Methods that validate whether an item can be borrowed or returned.
- A common function that processes different library-item objects polymorphically.
12. Interview Questions and Answers
Q1. What is polymorphism in Python?
Polymorphism allows different objects to respond to the same operation or method call in different ways. It can be implemented using method overriding and duck typing, among other techniques.
Q2. What is method overriding?
Method overriding occurs when a subclass defines a method with the same name as an inherited method, providing its own implementation.
Q3. What is duck typing?
Duck typing means that code focuses on the operations an object supports rather than requiring the object to belong to a particular class hierarchy.
Q4. Does Python support traditional method overloading?
Python does not support traditional signature-based method overloading through multiple definitions with the same name in one class. Default arguments, *args, **kwargs, and dispatch tools can provide alternative patterns.
Q5. What is encapsulation?
Encapsulation combines data and related behavior inside a class and encourages controlled access to internal state through methods and properties.
Q6. What is the difference between _name and __name?
A single leading underscore is a convention that signals internal use. A double leading underscore triggers name mangling, which helps avoid accidental name conflicts but does not provide absolute privacy or security.
Q7. What is a property in Python?
A property allows a method to be accessed through attribute syntax. Properties can provide computed values, validation, and controlled attribute updates.
Q8. What is abstraction?
Abstraction exposes essential functionality while hiding unnecessary implementation details. It helps callers interact with a clear interface rather than manage internal operations.
Q9. What is an abstract class?
An abstract class can define abstract methods that concrete subclasses must implement. Python supports this through the abc module.
Q10. What is the purpose of @abstractmethod?
The @abstractmethod decorator marks a method as abstract, requiring a concrete subclass to provide an implementation before it can normally be instantiated.
Q11. Can an abstract class contain regular methods?
Yes. An abstract class can contain both abstract methods and fully implemented methods that subclasses inherit or extend.
Q12. Can abstraction exist without an abstract class?
Yes. Any design that exposes a simple interface while hiding unnecessary implementation details can apply abstraction.
Q13. How do encapsulation and abstraction differ?
Encapsulation focuses on combining data and behavior and managing access to internal state. Abstraction focuses on exposing essential functionality while hiding implementation complexity.
Q14. How do abstraction and polymorphism work together?
An abstract base class can define a common interface, while different subclasses implement that interface in different ways. Code can then call the common operation polymorphically.
Q15. Why are these three principles important?
They help create software that is modular, reusable, easier to test, and easier to extend. They also help keep object state consistent and make complex systems easier to work with.
13. Quick Revision Checklist
- Polymorphism: One interface or operation can produce different behavior for different objects.
- Method overriding: A subclass provides its own implementation of an inherited method.
- Duck typing: Code uses an object based on the behavior it supports.
- Encapsulation: Data and related methods are grouped together, with controlled access to internal state.
- Single underscore: Convention for internal use, not strict access restriction.
- Double underscore: Triggers name mangling to reduce accidental name conflicts.
- Properties: Provide controlled access to attributes through getter and setter behavior.
- Abstraction: Exposes essential behavior and hides unnecessary implementation details.
- Abstract base class: Defines a common contract for concrete implementations.
- @abstractmethod: Marks a method that concrete subclasses must implement.
- Combined use: Abstract interfaces, polymorphic implementations, and encapsulated state can work together in one application.
14. Conclusion
Polymorphism, encapsulation, and abstraction are foundational principles of Python object-oriented programming. Polymorphism allows different objects to support the same operation with different behavior. Encapsulation helps manage internal data and preserve valid state. Abstraction defines clear interfaces and hides unnecessary implementation details.
Applying these principles together helps developers create flexible and maintainable software. Practice with payment processing, employee management, bank accounts, and library systems to strengthen your understanding of how OOP principles work in real applications.
Continue learning through the Python Training Course at JustAcademy, visit the JustAcademy website, or register for a course demo.