Programming with PythonUnit 79 min read
OOP in Python: Classes, Objects, Inheritance & Polymorphism
Unit 7 of Programming with Python covers object-oriented programming (OOP) principles in Python, including classes, objects, inheritance, polymorphism, encapsulation, and abstraction, with real-world examples and visual traces of code execution.
TAKEAWAYS:
- OOP organizes code into reusable objects (instances of classes) that bundle data (attributes) and behavior (methods).
- Inheritance allows child classes to reuse parent class code, reducing redundancy (e.g.,
Vehicle→Car,Bike). - Polymorphism lets objects of different classes respond to the same method call in their own way (e.g.,
draw()forCirclevs.Square). - Python uses
__init__for constructors,@staticmethodfor utility methods, and@classmethodfor class-level operations. - Encapsulation hides internal state (use
_prefix for "protected" attributes) and exposes controlled access via methods. - OOP is used in real-world apps (e.g., eSewa’s transaction system, Daraz’s product catalog) to model complex systems modularly.
1. Introduction to OOP Concepts
OOP is a programming paradigm that models real-world entities as objects with attributes (data) and methods (functions). Key concepts:
- Class: A blueprint for creating objects (e.g.,
class BankAccount). - Object: An instance of a class (e.g.,
my_account = BankAccount()). - Attribute: Data stored in an object (e.g.,
balance). - Method: Functions defined inside a class (e.g.,
deposit()).
Why OOP?
- Modularity: Code is organized into reusable components.
- Scalability: Easy to extend (e.g., adding new features to
BankAccount). - Maintainability: Logic is encapsulated, reducing bugs.
2. Classes and Objects
Defining a Class
class BankAccount:
def __init__(self, account_holder, balance=0):
self.account_holder = account_holder # Attribute
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f"Deposited ${amount}. New balance: ${self.balance}")
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds!")
else:
self.balance -= amount
print(f"Withdrew ${amount}. New balance: ${self.balance}")
Creating Objects
# Create an object
account1 = BankAccount("Ramesh", 1000)
account2 = BankAccount("Sita", 500)
# Call methods
account1.deposit(200) # Output: Deposited $200. New balance: $1200
account2.withdraw(100) # Output: Withdrew $100. New balance: $400
Visual: Object Creation
classDiagram
class BankAccount {
-account_holder: str
-balance: float
+__init__(account_holder, balance)
+deposit(amount)
+withdraw(amount)
}
BankAccount <|-- account1
BankAccount <|-- account23. The __init__ Method (Constructor)
- Called automatically when an object is created.
- Initializes object attributes.
- Example:
BankAccountuses__init__to setaccount_holderandbalance.
Trace: Object Initialization
| Step | Code Executed | account1 State |
|---|---|---|
| 1 | account1 = BankAccount("Ramesh", 1000) |
account_holder="Ramesh", balance=1000 |
| 2 | __init__ runs |
Attributes initialized |
4. Inheritance
Allows a child class to inherit attributes/methods from a parent class.
Example: SavingsAccount inherits from BankAccount.
class SavingsAccount(BankAccount):
def __init__(self, account_holder, balance=0, interest_rate=0.05):
super().__init__(account_holder, balance) # Call parent's __init__
self.interest_rate = interest_rate
def add_interest(self):
interest = self.balance * self.interest_rate
self.balance += interest
print(f"Added ${interest:.2f} interest. New balance: ${self.balance:.2f}")
Visual: Inheritance Hierarchy
classDiagram
class BankAccount {
+__init__(account_holder, balance)
+deposit(amount)
+withdraw(amount)
}
class SavingsAccount {
+__init__(account_holder, balance, interest_rate)
+add_interest()
}
SavingsAccount <|-- BankAccountReal-World Example: eSewa’s Transaction System
- Parent Class:
Transaction- Attributes:
amount,sender,receiver - Methods:
process(),validate()
- Attributes:
- Child Classes:
ElectricityPayment(Transaction): Addsbilling_idanddue_date.MobileRecharge(Transaction): Addsnetworkandplan_type.
- Why? Reuses common logic (e.g.,
validate()) while customizing for each payment type.
5. Polymorphism
Same method name behaves differently based on the object.
Example: draw() for Circle vs. Square.
class Shape:
def draw(self):
raise NotImplementedError("Subclass must implement this!")
class Circle(Shape):
def draw(self):
print("Drawing a circle")
class Square(Shape):
def draw(self):
print("Drawing a square")
# Polymorphic behavior
shapes = [Circle(), Square()]
for shape in shapes:
shape.draw()
Output:
Drawing a circle
Drawing a square
Visual: Polymorphism in Action
flowchart TD
A["shape.draw()"] -->|"Circle"| B["Print 'Drawing a circle'"]
A -->|"Square"| C["Print 'Drawing a square'"]Real-World Example: Daraz’s Product Catalog
- Parent Class:
Product- Method:
display()
- Method:
- Child Classes:
Electronics(Product): Overridesdisplay()to show specs (e.g., RAM, storage).Clothing(Product): Overridesdisplay()to show size/color.
- Why? Daraz’s app calls
product.display()for any product, and the correct format appears.
6. Encapsulation and Abstraction
- Encapsulation: Hides data and exposes methods to interact with it.
- Use
_prefix for "protected" attributes (e.g.,_balance).
- Use
- Abstraction: Shows only essential features (e.g.,
BankAccount.withdraw()hides internal checks).
Example: Protected Attributes
class BankAccount:
def __init__(self, balance):
self._balance = balance # Protected attribute
def withdraw(self, amount):
if amount > self._balance:
print("Insufficient funds!")
else:
self._balance -= amount
print(f"Withdrew ${amount}. New balance: ${self._balance}")
Visual: Encapsulation
classDiagram
class BankAccount {
-_balance: float
+withdraw(amount)
}
note for BankAccount "External code cannot access _balance directly."7. Special Methods (__str__, __repr__)
__str__: Returns a user-friendly string (e.g., forprint()).__repr__: Returns a developer-friendly string (e.g., for debugging).
class BankAccount:
def __str__(self):
return f"Account(holder='{self.account_holder}', balance=${self.balance})"
def __repr__(self):
return f"BankAccount('{self.account_holder}', {self.balance})"
account = BankAccount("Ramesh", 1000)
print(account) # Uses __str__: Account(holder='Ramesh', balance=$1000)
8. Class Methods and Static Methods
@classmethod: Operates on the class, not the instance. Example:from_csv()to create objects from a file.@staticmethod: Utility function unrelated to class/instance. Example:validate_amount(amount).
class BankAccount:
@classmethod
def from_csv(cls, data):
return cls(data["holder"], data["balance"])
@staticmethod
def validate_amount(amount):
return amount >= 0
# Usage
data = {"holder": "Sita", "balance": 500}
account = BankAccount.from_csv(data)
9. Composition vs. Inheritance
| Feature | Inheritance | Composition |
|---|---|---|
| Relationship | "Is-a" (e.g., SavingsAccount is a BankAccount) |
"Has-a" (e.g., Car has a Engine) |
| Use Case | Extend functionality | Reuse existing classes |
| Flexibility | Less flexible (tight coupling) | More flexible (loose coupling) |
Example: Composition
class Engine:
def start(self):
print("Engine started")
class Car:
def __init__(self):
self.engine = Engine() # Composition
def start(self):
self.engine.start()
10. Real-World Applications in Nepal
eSewa:
- OOP Use:
User,Transaction,PaymentGatewayclasses. - Polymorphism: Different payment methods (
UPI,CreditCard) implementprocess_payment().
- OOP Use:
Ncell’s Billing System:
- Inheritance:
Customer→PrepaidCustomer,PostpaidCustomer. - Encapsulation: Hides
balanceand exposesrecharge()/pay_bill().
- Inheritance:
NEPSE Stock Market:
- Composition:
Stockhas aTradingHistoryobject. - Abstraction:
Tradeclass hides complex order-matching logic.
- Composition:
Exam Tip
- Code Tracing: Expect questions on how objects are created and methods called (e.g., "What is the output of
account.withdraw(200)?"). - Diagrams: Draw class diagrams for inheritance/polymorphism (e.g.,
Vehicle→Car,Bike). - Error Spotting: Identify missing
__init__, incorrectsuper()calls, or broken encapsulation. - Real-World Links: Relate OOP to apps like eSewa/Khalti (e.g., "How would you model a
Userclass?"). - Short Answer: Define terms like inheritance, polymorphism, and encapsulation concisely.
Practice Question:
Write a Library class with Book and Member subclasses. Include:
- A
Bookwithtitle,author, andis_checked_out. - A
Memberwithnameandborrowed_books(a list). - Methods:
check_out_book(),return_book(). Trace: Show the state ofmember1.borrowed_booksafter checking out 2 books.
Based on the TU BIM syllabus for Programming with Python (IT243), unit 7.
Discussion
Loading…