Introduction
Object-Oriented Programming (OOP) is one of the most popular programming paradigms used in modern software development. It allows you to model real-world entities using classes and objects, making code reusable, modular, and scalable. In this blog post, we will explore Python's OOP concepts from basic to advanced, using a single use-case example: building an inventory management system for an online store.
Why Use Object-Oriented Programming?
- Modularity: Code is broken into self-contained modules.
- Reusability: Once a class is written, you can use it anywhere in your program or future projects.
- Scalability: OOP allows programs to grow by adding new classes or extending existing ones.
- Maintainability: Easier to manage, debug, and extend.
Basic Concepts: Classes and Objects
What Are Classes and Objects?
- Class: A blueprint for creating objects (instances). It defines a set of attributes (data) and methods (functions) that the objects will have.
- Object: An instance of a class. You can think of objects as real-world entities that have state and behavior.
Example: Inventory Item Class
Let’s start by creating a class to represent an item in our online store's inventory. Each item has a name, price, and quantity.
class InventoryItem:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
def get_total_price(self):
return self.price * self.quantity
# Creating objects of the class
item1 = InventoryItem("Laptop", 1000, 5)
item2 = InventoryItem("Smartphone", 500, 10)
# Accessing attributes and methods
print(f"Item: {item1.name}, Total Price: ${item1.get_total_price()}")
Explanation:
- The
__init__
method is the constructor, used to initialize the object’s attributes. -
self
refers to the current instance of the class, allowing each object to maintain its own state. - The method
get_total_price()
calculates the total price based on the item’s price and quantity.
Encapsulation: Controlling Access to Data
What is Encapsulation?
Encapsulation restricts direct access to an object's attributes to prevent accidental modification. Instead, you interact with the object's data through methods.
Example: Using Getters and Setters
We can improve the InventoryItem
class by making the attributes private and controlling access using getter and setter methods.
class InventoryItem:
def __init__(self, name, price, quantity):
self.__name = name # Private attribute
self.__price = price
self.__quantity = quantity
def get_total_price(self):
return self.__price * self.__quantity
# Getter methods
def get_name(self):
return self.__name
def get_price(self):
return self.__price
def get_quantity(self):
return self.__quantity
# Setter methods
def set_price(self, new_price):
if new_price > 0:
self.__price = new_price
def set_quantity(self, new_quantity):
if new_quantity >= 0:
self.__quantity = new_quantity
# Example usage
item = InventoryItem("Tablet", 300, 20)
item.set_price(350) # Update price using the setter method
print(f"Updated Price of {item.get_name()}: ${item.get_price()}")
Explanation:
- Attributes are now private (
__name
,__price
,__quantity
). - You access or modify these attributes using getter and setter methods, protecting the internal state of the object.
Inheritance: Reusing Code in New Classes
What is Inheritance?
Inheritance allows one class (child) to inherit attributes and methods from another class (parent). This promotes code reusability and hierarchy modeling.
Example: Extending the Inventory System
Let’s extend our system to handle different types of inventory items, such as PerishableItem and NonPerishableItem.
class InventoryItem:
def __init__(self, name, price, quantity):
self.__name = name
self.__price = price
self.__quantity = quantity
def get_total_price(self):
return self.__price * self.__quantity
def get_name(self):
return self.__name
class PerishableItem(InventoryItem):
def __init__(self, name, price, quantity, expiration_date):
super().__init__(name, price, quantity)
self.__expiration_date = expiration_date
def get_expiration_date(self):
return self.__expiration_date
class NonPerishableItem(InventoryItem):
def __init__(self, name, price, quantity):
super().__init__(name, price, quantity)
# Example usage
milk = PerishableItem("Milk", 2, 30, "2024-09-30")
laptop = NonPerishableItem("Laptop", 1000, 10)
print(f"{milk.get_name()} expires on {milk.get_expiration_date()}")
print(f"{laptop.get_name()} costs ${laptop.get_total_price()}")
Explanation:
-
PerishableItem
andNonPerishableItem
inherit fromInventoryItem
. - The
super()
function is used to call the parent class's constructor (__init__
) to initialize common attributes. -
PerishableItem
adds the new attributeexpiration_date
.
Polymorphism: Interchangeable Objects
What is Polymorphism?
Polymorphism allows objects of different classes to be treated as instances of a common superclass. It enables you to define methods in the parent class that are overridden in the child classes, but you can call them without knowing the exact class.
Example: Polymorphic Behavior
Let’s modify our system so that we can display different information based on the item type.
class InventoryItem:
def __init__(self, name, price, quantity):
self.__name = name
self.__price = price
self.__quantity = quantity
def display_info(self):
return f"Item: {self.__name}, Total Price: ${self.get_total_price()}"
def get_total_price(self):
return self.__price * self.__quantity
class PerishableItem(InventoryItem):
def __init__(self, name, price, quantity, expiration_date):
super().__init__(name, price, quantity)
self.__expiration_date = expiration_date
def display_info(self):
return f"Perishable Item: {self.get_name()}, Expires on: {self.__expiration_date}"
class NonPerishableItem(InventoryItem):
def display_info(self):
return f"Non-Perishable Item: {self.get_name()}, Price: ${self.get_total_price()}"
# Example usage
items = [
PerishableItem("Milk", 2, 30, "2024-09-30"),
NonPerishableItem("Laptop", 1000, 10)
]
for item in items:
print(item.display_info()) # Polymorphic method call
Explanation:
- The
display_info()
method is overridden in bothPerishableItem
andNonPerishableItem
. - When you call
display_info()
on an object, Python uses the appropriate method depending on the object’s type, even if the specific type is unknown at runtime.
Advanced Concepts: Decorators and Class Methods
Class Methods and Static Methods
-
Class methods are methods that belong to the class itself, not to any object. They are marked with
@classmethod
. -
Static methods do not access or modify class or instance-specific data. They are marked with
@staticmethod
.
Example: Managing Inventory with Class Methods
Let’s add a class-level method to track the total number of items in the inventory.
class InventoryItem:
total_items = 0 # Class attribute to keep track of all items
def __init__(self, name, price, quantity):
self.__name = name
self.__price = price
self.__quantity = quantity
InventoryItem.total_items += quantity # Update total items
@classmethod
def get_total_items(cls):
return cls.total_items
# Example usage
item1 = InventoryItem("Laptop", 1000, 5)
item2 = InventoryItem("Smartphone", 500, 10)
print(f"Total items in inventory: {InventoryItem.get_total_items()}")
Explanation:
-
total_items
is a class attribute shared by all instances. - The class method
get_total_items()
allows us to access this shared data.
Decorators in OOP
Decorators in OOP are often used to modify the behavior of methods. For example, we can create a decorator to automatically log method calls in our inventory system.
def log_method_call(func):
def wrapper(*args, **kwargs):
print(f"Calling method {func.__name__}")
return func(*args, **kwargs)
return wrapper
class InventoryItem:
def __init__(self, name, price, quantity):
self.__name = name
self.__price = price
self.__quantity = quantity
@log_method_call
def get_total_price(self):
return self.__price * self.__quantity
# Example usage
item = InventoryItem("Tablet", 300, 10)
print(item.get
_total_price()) # Logs the method call
Conclusion
Object-Oriented Programming in Python offers a powerful and flexible way to organize and structure your code. By using classes, inheritance, encapsulation, and polymorphism, you can model complex systems and enhance code reusability. In this post, we walked through key OOP concepts, gradually building an inventory management system to demonstrate each principle.
OOP in Python makes your code more readable, maintainable, and easier to extend as the complexity of your application grows. Happy coding!
Top comments (0)