Session-3-1-Examples

This page shows the source code for Session-3-1-Examples.py in browser-friendly HTML format. It was generated automatically from the original Python file.

Source File Session-3-1-Examples.py
Folder Chapter-3-Advanced-Sessions
"""
Advanced Python - Session 1: Object-Oriented Programming
Code Examples and Library Management System
"""

from abc import ABC, abstractmethod
from datetime import datetime, timedelta
import csv

# ============================================
# PART 1: Classes and Objects Basics
# ============================================
print("=" * 60)
print("PART 1: Classes and Objects")
print("=" * 60)

class Dog:
    """A simple Dog class"""
    
    # Class variable (shared by all instances)
    species = "Canis familiaris"
    
    def __init__(self, name, age, breed):
        """Constructor - initializes instance variables"""
        self.name = name      # Instance variable
        self.age = age
        self.breed = breed
    
    def bark(self):
        """Instance method"""
        return f"{self.name} says Woof!"
    
    def get_info(self):
        """Return dog information"""
        return f"{self.name} is a {self.age}-year-old {self.breed}"
    
    def birthday(self):
        """Increment age"""
        self.age += 1
        return f"Happy Birthday {self.name}! Now {self.age} years old."

# Creating objects (instances)
print("\n--- Creating Dog Objects ---")
dog1 = Dog("Buddy", 3, "Golden Retriever")
dog2 = Dog("Max", 5, "German Shepherd")

print(dog1.bark())
print(dog2.get_info())
print(dog1.birthday())
print(f"Species: {dog1.species}")

# ============================================
# PART 2: Inheritance
# ============================================
print("\n" + "=" * 60)
print("PART 2: Inheritance")
print("=" * 60)

class Animal:
    """Base class for all animals"""
    
    def __init__(self, name, species):
        self.name = name
        self.species = species
        self.is_alive = True
    
    def speak(self):
        return "Some generic animal sound"
    
    def get_info(self):
        return f"{self.name} is a {self.species}"

class Cat(Animal):
    """Cat class inheriting from Animal"""
    
    def __init__(self, name, color):
        super().__init__(name, "Cat")  # Call parent constructor
        self.color = color
    
    def speak(self):
        """Override parent method"""
        return f"{self.name} says Meow!"
    
    def purr(self):
        """Cat-specific method"""
        return f"{self.name} is purring..."

class Bird(Animal):
    """Bird class inheriting from Animal"""
    
    def __init__(self, name, can_fly):
        super().__init__(name, "Bird")
        self.can_fly = can_fly
    
    def speak(self):
        return f"{self.name} says Tweet!"
    
    def fly(self):
        if self.can_fly:
            return f"{self.name} is flying!"
        return f"{self.name} cannot fly."

print("\n--- Animal Inheritance Example ---")
cat = Cat("Whiskers", "Orange")
bird = Bird("Tweety", True)

print(cat.get_info())   # Inherited method
print(cat.speak())      # Overridden method
print(cat.purr())       # Cat-specific method

print(bird.get_info())
print(bird.speak())
print(bird.fly())

# ============================================
# PART 3: Encapsulation
# ============================================
print("\n" + "=" * 60)
print("PART 3: Encapsulation")
print("=" * 60)

class BankAccount:
    """Bank account with encapsulation"""
    
    def __init__(self, owner, initial_balance=0):
        self.owner = owner                    # Public
        self._account_number = "ACC123456"    # Protected (convention)
        self.__balance = initial_balance      # Private (name mangling)
    
    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 amount > self.__balance:
            return "Insufficient funds"
        if amount > 0:
            self.__balance -= amount
            return f"Withdrew ${amount}. New balance: ${self.__balance}"
        return "Invalid withdrawal amount"
    
    def get_balance(self):
        """Public method to check balance"""
        return self.__balance
    
    def __str__(self):
        return f"Account({self.owner}): ${self.__balance}"

print("\n--- Bank Account Example ---")
account = BankAccount("Alice", 1000)
print(account.deposit(500))
print(account.withdraw(300))
print(f"Current balance: ${account.get_balance()}")
# print(account.__balance)  # This would cause an error!

# ============================================
# PART 4: Property Decorators
# ============================================
print("\n" + "=" * 60)
print("PART 4: Property Decorators")
print("=" * 60)

class Temperature:
    """Temperature class with property decorators"""
    
    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 with validation"""
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """Calculated property"""
        return (self._celsius * 9/5) + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        """Set celsius from fahrenheit"""
        self._celsius = (value - 32) * 5/9

print("\n--- Temperature Property Example ---")
temp = Temperature(25)
print(f"Temperature: {temp.celsius}°C = {temp.fahrenheit}°F")
temp.fahrenheit = 86
print(f"New Temperature: {temp.celsius}°C = {temp.fahrenheit}°F")

# ============================================
# PART 5: Magic Methods (Dunder Methods)
# ============================================
print("\n" + "=" * 60)
print("PART 5: Magic Methods")
print("=" * 60)

class Book:
    """Book class with magic methods"""
    
    def __init__(self, title, author, pages, price):
        self.title = title
        self.author = author
        self.pages = pages
        self.price = price
    
    def __str__(self):
        """User-friendly string representation"""
        return f"'{self.title}' by {self.author}"
    
    def __repr__(self):
        """Developer-friendly representation"""
        return f"Book('{self.title}', '{self.author}', {self.pages}, ${self.price})"
    
    def __len__(self):
        """Return number of pages"""
        return self.pages
    
    def __eq__(self, other):
        """Check equality"""
        return (self.title == other.title and 
                self.author == other.author)
    
    def __lt__(self, other):
        """Less than comparison (by pages)"""
        return self.pages < other.pages
    
    def __add__(self, other):
        """Add books (combine pages)"""
        return self.pages + other.pages

print("\n--- Magic Methods Example ---")
book1 = Book("1984", "George Orwell", 328, 15.99)
book2 = Book("Animal Farm", "George Orwell", 112, 12.99)
book3 = Book("1984", "George Orwell", 328, 15.99)

print(str(book1))           # Calls __str__
print(repr(book1))          # Calls __repr__
print(f"Pages: {len(book1)}")  # Calls __len__
print(f"book1 == book3: {book1 == book3}")  # Calls __eq__
print(f"book1 < book2: {book1 < book2}")    # Calls __lt__
print(f"Total pages: {book1 + book2}")      # Calls __add__

# ============================================
# PART 6: Class Methods and Static Methods
# ============================================
print("\n" + "=" * 60)
print("PART 6: Class Methods and Static Methods")
print("=" * 60)

class Employee:
    """Employee class with class and static methods"""
    
    num_employees = 0
    raise_amount = 1.04
    
    def __init__(self, first, last, salary):
        self.first = first
        self.last = last
        self.salary = salary
        Employee.num_employees += 1
    
    @property
    def email(self):
        """Generate email"""
        return f"{self.first.lower()}.{self.last.lower()}@company.com"
    
    @property
    def fullname(self):
        return f"{self.first} {self.last}"
    
    def apply_raise(self):
        """Instance method"""
        self.salary = int(self.salary * self.raise_amount)
    
    @classmethod
    def set_raise_amount(cls, amount):
        """Class method to modify class variable"""
        cls.raise_amount = amount
    
    @classmethod
    def from_string(cls, emp_string):
        """Alternative constructor"""
        first, last, salary = emp_string.split('-')
        return cls(first, last, int(salary))
    
    @staticmethod
    def is_workday(day):
        """Static method - doesn't need instance or class"""
        if day.weekday() == 5 or day.weekday() == 6:
            return False
        return True
    
    def __str__(self):
        return f"{self.fullname}: ${self.salary}"

print("\n--- Employee Class Example ---")
emp1 = Employee("John", "Doe", 50000)
emp2 = Employee.from_string("Jane-Smith-60000")  # Using class method

print(emp1)
print(f"Email: {emp1.email}")
print(f"Total employees: {Employee.num_employees}")

emp1.apply_raise()
print(f"After raise: {emp1}")

# Using static method
import datetime
my_date = datetime.date(2025, 1, 20)
print(f"Is {my_date} a workday? {Employee.is_workday(my_date)}")

# ============================================
# PART 7: Abstract Classes
# ============================================
print("\n" + "=" * 60)
print("PART 7: Abstract Classes")
print("=" * 60)

class Shape(ABC):
    """Abstract base class for shapes"""
    
    def __init__(self, name):
        self.name = name
    
    @abstractmethod
    def area(self):
        """Must be implemented by subclasses"""
        pass
    
    @abstractmethod
    def perimeter(self):
        """Must be implemented by subclasses"""
        pass
    
    def describe(self):
        """Concrete method"""
        return f"This is a {self.name}"

class Rectangle(Shape):
    def __init__(self, width, height):
        super().__init__("Rectangle")
        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):
        super().__init__("Circle")
        self.radius = radius
    
    def area(self):
        return 3.14159 * self.radius ** 2
    
    def perimeter(self):
        return 2 * 3.14159 * self.radius

print("\n--- Shape Abstract Class Example ---")
rect = Rectangle(5, 10)
circle = Circle(7)

print(f"{rect.describe()}: Area = {rect.area()}, Perimeter = {rect.perimeter()}")
print(f"{circle.describe()}: Area = {circle.area():.2f}, Circumference = {circle.perimeter():.2f}")

# ============================================
# PART 8: Complete Project - Library Management System
# ============================================
print("\n" + "=" * 60)
print("PART 8: Library Management System")
print("=" * 60)

class Book:
    """Represents a book in the library"""
    
    def __init__(self, isbn, title, author, year, genre):
        self.isbn = isbn
        self.title = title
        self.author = author
        self.year = year
        self.genre = genre
        self.is_available = True
        self.borrowed_by = None
        self.due_date = None
    
    def __str__(self):
        status = "Available" if self.is_available else f"Borrowed (due: {self.due_date})"
        return f"[{self.isbn}] '{self.title}' by {self.author} ({self.year}) - {status}"
    
    def __repr__(self):
        return f"Book('{self.isbn}', '{self.title}', '{self.author}')"

class Member:
    """Represents a library member"""
    
    def __init__(self, member_id, name, email):
        self.member_id = member_id
        self.name = name
        self.email = email
        self.borrowed_books = []
        self.join_date = datetime.now()
    
    def can_borrow(self):
        """Check if member can borrow more books"""
        return len(self.borrowed_books) < 3
    
    def __str__(self):
        return f"Member {self.member_id}: {self.name} ({len(self.borrowed_books)} books borrowed)"
    
    def __repr__(self):
        return f"Member('{self.member_id}', '{self.name}')"

class Library:
    """Main library management system"""
    
    def __init__(self, name):
        self.name = name
        self.books = {}      # ISBN -> Book
        self.members = {}    # member_id -> Member
    
    def add_book(self, book):
        """Add a book to the library"""
        if book.isbn in self.books:
            return f"Book with ISBN {book.isbn} already exists"
        self.books[book.isbn] = book
        return f"Added: {book.title}"
    
    def register_member(self, member):
        """Register a new member"""
        if member.member_id in self.members:
            return f"Member {member.member_id} already registered"
        self.members[member.member_id] = member
        return f"Registered: {member.name}"
    
    def borrow_book(self, member_id, isbn):
        """Process book borrowing"""
        # Validation
        if member_id not in self.members:
            return "Member not found"
        if isbn not in self.books:
            return "Book not found"
        
        member = self.members[member_id]
        book = self.books[isbn]
        
        # Check availability
        if not book.is_available:
            return f"'{book.title}' is currently borrowed"
        
        # Check member limit
        if not member.can_borrow():
            return f"{member.name} has reached borrowing limit (3 books)"
        
        # Process borrowing
        book.is_available = False
        book.borrowed_by = member_id
        book.due_date = (datetime.now() + timedelta(days=14)).strftime("%Y-%m-%d")
        member.borrowed_books.append(isbn)
        
        return f"{member.name} borrowed '{book.title}' (due: {book.due_date})"
    
    def return_book(self, member_id, isbn):
        """Process book return"""
        if member_id not in self.members:
            return "Member not found"
        if isbn not in self.books:
            return "Book not found"
        
        member = self.members[member_id]
        book = self.books[isbn]
        
        if book.is_available:
            return f"'{book.title}' was not borrowed"
        
        if isbn not in member.borrowed_books:
            return f"{member.name} did not borrow this book"
        
        # Process return
        book.is_available = True
        book.borrowed_by = None
        book.due_date = None
        member.borrowed_books.remove(isbn)
        
        return f"{member.name} returned '{book.title}'"
    
    def search_books(self, query):
        """Search books by title or author"""
        results = []
        query_lower = query.lower()
        for book in self.books.values():
            if (query_lower in book.title.lower() or 
                query_lower in book.author.lower()):
                results.append(book)
        return results
    
    def get_available_books(self):
        """Get all available books"""
        return [book for book in self.books.values() if book.is_available]
    
    def get_member_books(self, member_id):
        """Get books borrowed by a member"""
        if member_id not in self.members:
            return []
        member = self.members[member_id]
        return [self.books[isbn] for isbn in member.borrowed_books]
    
    def generate_report(self):
        """Generate library statistics"""
        total_books = len(self.books)
        available_books = len(self.get_available_books())
        borrowed_books = total_books - available_books
        total_members = len(self.members)
        
        report = f"\n{'='*50}\n"
        report += f"LIBRARY REPORT: {self.name}\n"
        report += f"{'='*50}\n"
        report += f"Total Books: {total_books}\n"
        report += f"Available: {available_books}\n"
        report += f"Borrowed: {borrowed_books}\n"
        report += f"Total Members: {total_members}\n"
        report += f"{'='*50}\n"
        return report
    
    def load_books_from_csv(self, filename):
        """Load books from CSV file"""
        count = 0
        try:
            with open(filename, 'r') as file:
                reader = csv.DictReader(file)
                for row in reader:
                    book = Book(
                        row['isbn'],
                        row['title'],
                        row['author'],
                        int(row['year']),
                        row['genre']
                    )
                    self.add_book(book)
                    count += 1
            return f"Loaded {count} books from {filename}"
        except FileNotFoundError:
            return f"File {filename} not found"

# Demo the Library Management System
print("\n--- Library System Demo ---")

# Create library
library = Library("City Central Library")

# Load books from CSV
print(library.load_books_from_csv("books.csv"))

# Register members
member1 = Member("M001", "Alice Johnson", "alice@email.com")
member2 = Member("M002", "Bob Smith", "bob@email.com")

print(library.register_member(member1))
print(library.register_member(member2))

# Borrow books
print("\n--- Borrowing Books ---")
print(library.borrow_book("M001", "978-0-1234-5678-0"))
print(library.borrow_book("M001", "978-0-1234-5679-7"))
print(library.borrow_book("M002", "978-0-1234-5680-3"))

# Try to borrow unavailable book
print(library.borrow_book("M002", "978-0-1234-5678-0"))

# Check member's borrowed books
print("\n--- Alice's Borrowed Books ---")
alice_books = library.get_member_books("M001")
for book in alice_books:
    print(f"  - {book}")

# Return a book
print("\n--- Returning Books ---")
print(library.return_book("M001", "978-0-1234-5678-0"))

# Search books
print("\n--- Search Results for 'Python' ---")
results = library.search_books("Python")
for book in results:
    print(f"  - {book}")

# Generate report
print(library.generate_report())

print("\n" + "=" * 60)
print("Advanced Session 1 Completed!")
print("Master OOP to build professional applications!")
print("=" * 60)