Python Topic
Python Object-Oriented Programming (OOP)
1. Encapsulation
Bundling data and methods that operate on that data within a single unit (class), and restricting direct access to some components.
2. Inheritance
Creating new classes (child classes) from existing ones (parent classes), inheriting attributes and methods.
3. Polymorphism
Ability to present the same interface for different underlying forms (data types).
4. Abstraction
Hiding complex implementation details and showing only essential features.
Classes and Objects
Basic Class Definition
python
class Dog:
# Class attribute - shared by all instances
species = "Canis familiaris"
# Constructor method - called when object is created
def __init__(self, name, age, breed):
# Instance attributes - unique to each instance
self.name = name
self.age = age
self.breed = breed
self._is_sleeping = False # Protected attribute (convention)
# Instance method
def bark(self):
return f"{self.name} says Woof!"
def sleep(self):
self._is_sleeping = True
return f"{self.name} is sleeping"
def wake_up(self):
self._is_sleeping = False
return f"{self.name} woke up"
# String representation
def __str__(self):
return f"{self.name} is a {self.age}-year-old {self.breed}"
# Formal string representation
def __repr__(self):
return f"Dog('{self.name}', {self.age}, '{self.breed}')"
# Creating objects (instances)
dog1 = Dog("Buddy", 3, "Golden Retriever")
dog2 = Dog("Max", 5, "German Shepherd")
print(dog1.bark()) # Buddy says Woof!
print(dog2) # Max is a 5-year-old German Shepherd
print(f"Species: {Dog.species}") # Accessing class attribute
Encapsulation in Detail
python
class BankAccount:
def __init__(self, account_holder, initial_balance=0):
self.account_holder = account_holder
self._account_number = self._generate_account_number() # Protected
self.__balance = initial_balance # Private (name mangling)
def _generate_account_number(self):
"""Protected method - for internal use"""
import random
return f"ACC{random.randint(10000, 99999)}"
def deposit(self, amount):
"""Public method to deposit money"""
if amount > 0:
self.__balance += amount
return f"Deposited ${amount}. New balance: ${self.__balance}"
return "Invalid deposit amount"
def withdraw(self, amount):
"""Public method to withdraw money"""
if 0 < amount <= self.__balance:
self.__balance -= amount
return f"Withdrew ${amount}. New balance: ${self.__balance}"
return "Insufficient funds or invalid amount"
# Getter method for balance (controlled access)
def get_balance(self):
return self.__balance
# Property decorator - Pythonic way for getters/setters
@property
def balance(self):
return self.__balance
@property
def account_info(self):
return f"Account: {self._account_number}, Holder: {self.account_holder}"
# Usage
account = BankAccount("John Doe", 1000)
print(account.deposit(500)) # Deposited $500. New balance: $1500
print(account.withdraw(200)) # Withdrew $200. New balance: $1300
print(account.get_balance()) # 1300
print(account.balance) # 1300 (using property)
print(account.account_info) # Account: ACCXXXXX, Holder: John Doe
# Trying to access private attribute directly (will fail)
# print(account.__balance) # AttributeError
Inheritance in Detail
Single Inheritance
python
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def speak(self):
raise NotImplementedError("Subclass must implement this method")
def move(self):
return f"{self.name} is moving"
def __str__(self):
return f"{self.name} the {self.species}"
class Cat(Animal): # Inherits from Animal
def __init__(self, name, breed, is_indoor=True):
super().__init__(name, "Cat") # Call parent constructor
self.breed = breed
self.is_indoor = is_indoor
# Method overriding
def speak(self):
return "Meow!"
# Additional method specific to Cat
def purr(self):
return "Purrrrr..."
def __str__(self):
return f"{super().__str__()} ({self.breed})"
class Dog(Animal): # Inherits from Animal
def __init__(self, name, breed):
super().__init__(name, "Dog")
self.breed = breed
def speak(self):
return "Woof!"
def fetch(self):
return f"{self.name} is fetching the ball"
# Usage
cat = Cat("Whiskers", "Siamese")
dog = Dog("Buddy", "Labrador")
print(cat.speak()) # Meow!
print(dog.speak()) # Woof!
print(cat.move()) # Whiskers is moving
print(cat.purr()) # Purrrrr...
print(dog.fetch()) # Buddy is fetching the ball
print(cat) # Whiskers the Cat (Siamese)
Multiple Inheritance
python
class Flyable:
def __init__(self, max_altitude):
self.max_altitude = max_altitude
self.is_flying = False
def take_off(self):
self.is_flying = True
return "Taking off"
def land(self):
self.is_flying = False
return "Landing"
def fly(self):
return "Flying high"
class Swimmable:
def __init__(self, max_depth):
self.max_depth = max_depth
self.is_swimming = False
def dive(self):
self.is_swimming = True
return "Diving into water"
def surface(self):
self.is_swimming = False
return "Surfacing"
def swim(self):
return "Swimming"
class Duck(Animal, Flyable, Swimmable): # Multiple inheritance
def __init__(self, name):
Animal.__init__(self, name, "Duck")
Flyable.__init__(self, 1000) # 1000 meters max altitude
Swimmable.__init__(self, 10) # 10 meters max depth
def speak(self):
return "Quack!"
def display_abilities(self):
return f"I can: {self.fly()} and {self.swim()}"
# Usage
duck = Duck("Donald")
print(duck.speak()) # Quack!
print(duck.take_off()) # Taking off
print(duck.fly()) # Flying high
print(duck.dive()) # Diving into water
print(duck.display_abilities()) # I can: Flying high and Swimming
Polymorphism in Detail
Method Overriding
python
class Shape:
def area(self):
raise NotImplementedError("Subclass must implement area()")
def perimeter(self):
raise NotImplementedError("Subclass must implement perimeter()")
def __str__(self):
return self.__class__.__name__
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
class Triangle(Shape):
def __init__(self, base, height, side1, side2, side3):
self.base = base
self.height = height
self.side1 = side1
self.side2 = side2
self.side3 = side3
def area(self):
return 0.5 * self.base * self.height
def perimeter(self):
return self.side1 + self.side2 + self.side3
# Polymorphic function - works with any Shape
def print_shape_info(shape):
print(f"{shape}: Area = {shape.area():.2f}, Perimeter = {shape.perimeter():.2f}")
# Usage
shapes = [
Rectangle(5, 3),
Circle(4),
Triangle(6, 4, 3, 4, 5)
]
for shape in shapes:
print_shape_info(shape)
# Output:
# Rectangle: Area = 15.00, Perimeter = 16.00
# Circle: Area = 50.27, Perimeter = 25.13
# Triangle: Area = 12.00, Perimeter = 12.00
Operator Overloading
python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# Operator overloading
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
# Usage
v1 = Vector(2, 3)
v2 = Vector(1, 4)
v3 = v1 + v2 # Uses __add__
v4 = v1 * 3 # Uses __mul__
print(v1) # Vector(2, 3)
print(v2) # Vector(1, 4)
print(v3) # Vector(3, 7)
print(v4) # Vector(6, 9)
print(v1.magnitude()) # 3.605551275463989
print(v1 == v2) # False
Advanced OOP Concepts
Abstract Base Classes
python
from abc import ABC, abstractmethod
class Employee(ABC):
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
@abstractmethod
def calculate_salary(self):
pass
@abstractmethod
def get_role(self):
pass
def get_info(self):
return f"ID: {self.employee_id}, Name: {self.name}"
class FullTimeEmployee(Employee):
def __init__(self, name, employee_id, monthly_salary):
super().__init__(name, employee_id)
self.monthly_salary = monthly_salary
def calculate_salary(self):
return self.monthly_salary
def get_role(self):
return "Full-time Employee"
class PartTimeEmployee(Employee):
def __init__(self, name, employee_id, hourly_rate, hours_worked):
super().__init__(name, employee_id)
self.hourly_rate = hourly_rate
self.hours_worked = hours_worked
def calculate_salary(self):
return self.hourly_rate * self.hours_worked
def get_role(self):
return "Part-time Employee"
class Contractor(Employee):
def __init__(self, name, employee_id, project_fee):
super().__init__(name, employee_id)
self.project_fee = project_fee
def calculate_salary(self):
return self.project_fee
def get_role(self):
return "Contractor"
# Usage
employees = [
FullTimeEmployee("Alice", "FT001", 5000),
PartTimeEmployee("Bob", "PT001", 25, 80),
Contractor("Charlie", "C001", 10000)
]
for emp in employees:
print(f"{emp.get_info()} - {emp.get_role()} - Salary: ${emp.calculate_salary()}")
Class Methods and Static Methods
python
class Student:
# Class attribute
school_name = "ABC High School"
total_students = 0
def __init__(self, name, grade, student_id):
self.name = name
self.grade = grade
self.student_id = student_id
Student.total_students += 1 # Modify class attribute
# Instance method
def get_info(self):
return f"{self.name} (ID: {self.student_id}), Grade: {self.grade}"
# Class method - works with class, not instance
@classmethod
def get_total_students(cls):
return cls.total_students
@classmethod
def create_from_string(cls, student_str):
"""Alternative constructor"""
name, grade, student_id = student_str.split(",")
return cls(name, int(grade), student_id)
# Static method - doesn't need class or instance
@staticmethod
def is_valid_grade(grade):
return 1 <= grade <= 12
@staticmethod
def get_school_motto():
return "Strive for Excellence"
# Usage
student1 = Student("John", 10, "S001")
student2 = Student("Sarah", 11, "S002")
# Using class method
print(f"Total students: {Student.get_total_students()}") # Total students: 2
# Using alternative constructor
student3 = Student.create_from_string("Mike,9,S003")
print(student3.get_info()) # Mike (ID: S003), Grade: 9
# Using static methods
print(Student.is_valid_grade(15)) # False
print(Student.get_school_motto()) # Strive for Excellence
Property Decorators for Getters/Setters
python
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""Getter for celsius"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""Setter for celsius with validation"""
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
"""Computed property - fahrenheit"""
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
"""Setter for fahrenheit"""
self._celsius = (value - 32) * 5/9
@property
def kelvin(self):
"""Computed property - kelvin"""
return self._celsius + 273.15
# Usage
temp = Temperature(25)
print(f"Celsius: {temp.celsius}") # Celsius: 25
print(f"Fahrenheit: {temp.fahrenheit}") # Fahrenheit: 77.0
print(f"Kelvin: {temp.kelvin}") # Kelvin: 298.15
# Using setters
temp.celsius = 30
print(f"New Fahrenheit: {temp.fahrenheit}") # New Fahrenheit: 86.0
temp.fahrenheit = 100
print(f"New Celsius: {temp.celsius}") # New Celsius: 37.777...
Real-World Example: E-commerce System
python
class Product:
def __init__(self, product_id, name, price, quantity):
self.product_id = product_id
self.name = name
self.price = price
self.quantity = quantity
def update_quantity(self, amount):
self.quantity += amount
def __str__(self):
return f"{self.name} - ${self.price} (Stock: {self.quantity})"
class Customer:
def __init__(self, customer_id, name, email):
self.customer_id = customer_id
self.name = name
self.email = email
self._orders = []
def place_order(self, cart):
order = Order(self, cart.items)
self._orders.append(order)
return order
def get_order_history(self):
return self._orders
def __str__(self):
return f"Customer: {self.name} ({self.email})"
class Cart:
def __init__(self):
self.items = {} # {product: quantity}
def add_item(self, product, quantity=1):
if product.quantity >= quantity:
if product in self.items:
self.items[product] += quantity
else:
self.items[product] = quantity
return True
return False
def remove_item(self, product, quantity=1):
if product in self.items:
if self.items[product] <= quantity:
del self.items[product]
else:
self.items[product] -= quantity
return True
return False
def get_total(self):
return sum(product.price * quantity for product, quantity in self.items.items())
def clear(self):
self.items.clear()
def __str__(self):
if not self.items:
return "Cart is empty"
items_str = "\n".join([f"{product.name} x{quantity}" for product, quantity in self.items.items()])
return f"Cart:\n{items_str}\nTotal: ${self.get_total():.2f}"
class Order:
order_counter = 0
def __init__(self, customer, items):
Order.order_counter += 1
self.order_id = f"ORD{Order.order_counter:04d}"
self.customer = customer
self.items = items.copy()
self.total = sum(product.price * quantity for product, quantity in items.items())
self.status = "Pending"
# Update product quantities
for product, quantity in items.items():
product.update_quantity(-quantity)
def complete_order(self):
self.status = "Completed"
def __str__(self):
items_str = "\n".join([f" {product.name} x{quantity}" for product, quantity in self.items.items()])
return f"Order {self.order_id} - {self.customer.name}\n{items_str}\nTotal: ${self.total:.2f} - Status: {self.status}"
# Usage Example
# Create products
laptop = Product("P001", "Laptop", 999.99, 10)
mouse = Product("P002", "Wireless Mouse", 29.99, 50)
keyboard = Product("P003", "Mechanical Keyboard", 79.99, 25)
# Create customer
customer = Customer("C001", "John Doe", "john@example.com")
# Create cart and add items
cart = Cart()
cart.add_item(laptop, 1)
cart.add_item(mouse, 2)
cart.add_item(keyboard, 1)
print("Before order:")
print(cart)
print(f"Laptop stock: {laptop.quantity}")
# Place order
order = customer.place_order(cart)
order.complete_order()
print("\nAfter order:")
print(order)
print(f"Laptop stock: {laptop.quantity}")
print(f"Cart: {cart}") # Cart should be empty after order
# Order history
print(f"\nOrder history for {customer.name}:")
for order in customer.get_order_history():
print(order)
This extensive OOP guide covers all the fundamental concepts with practical examples. The key to mastering OOP is understanding how to properly structure your code using these principles to create maintainable, reusable, and scalable applications.
Comments (0)
No comments yet. Be the first to comment!