String Handling in Python

Strings In Depth

Quick Recap

You already know strings from Stage 1:

name = "Gagan"
message = 'Hello World'

But strings in Python are much more powerful than just storing text. Let's go deep.


Strings are Sequences

A string is actually a sequence of characters — just like a list but for text. This means everything you learned about indexing and slicing on lists works on strings too:


    name = "Gagan"
    #       01234

    print(name[0])      # G
    print(name[1])      # a
    print(name[-1])     # n  (last character)
    print(name[1:4])    # aga (slicing)
    print(name[:3])     # Gag
    print(len(name))    # 5  (total characters)


Looping Through a String


    name = "Gagan"

    for char in name:
        print(char)

Output:

G
a
g
a
n

Strings are Immutable

Just like tuples, strings cannot be changed after creation:


    name = "Gagan"
    name[0] = "K"      # ERROR — strings are immutable

But you can create a new string from an old one:


    name = "Gagan"
    new_name = "K" + name[1:]    # 'K' + 'agan'
    print(new_name)               # Kagan


String Methods — The Real Power

Python gives you dozens of built-in methods to work with strings. These are what you'll use every single day in real projects.


Case Methods


    text = "Hello World"

    print(text.upper())       # HELLO WORLD
    print(text.lower())       # hello world
    print(text.title())       # Hello World  (first letter of each word capitalized)
    print(text.capitalize())  # Hello world  (only first letter of string)
    print(text.swapcase())    # hELLO wORLD  (swap upper/lower)

Real use case — case-insensitive comparison:


    username = input("Enter username: ")

    if username.lower() == "gagan":
        print("Welcome!")

Now it works whether user types "Gagan", "GAGAN", "gAgAn" — anything.


Whitespace Methods


    text = "   Hello World   "

    print(text.strip())       # "Hello World"   (removes both sides)
    print(text.lstrip())      # "Hello World   " (removes left side)
    print(text.rstrip())      # "   Hello World" (removes right side)

Real use case — user input always has accidental spaces:


    email = input("Enter email: ")
    email = email.strip().lower()    # clean the input before using it

Always strip and lowercase user input before processing. This is standard practice.


Search Methods


    text = "I love Python programming"

    print(text.find("Python"))        # 7  — index where it starts
    print(text.find("Java"))          # -1 — not found, returns -1
    print(text.count("o"))            # 3  — how many times 'o' appears
    print(text.startswith("I love"))  # True
    print(text.endswith("ing"))       # True
    print("Python" in text)           # True — simplest way to check


Replace Method


    text = "I love Java. Java is great."

    new_text = text.replace("Java", "Python")
    print(new_text)    # I love Python. Python is great.

    # Replace only first occurrence
    new_text2 = text.replace("Java", "Python", 1)
    print(new_text2)   # I love Python. Java is great.

Original string is never changed — a new string is returned.


Split and Join — Very Important

split() — breaks a string into a list:


    sentence = "I love Python programming"
    words = sentence.split()         # split by space (default)
    print(words)                     # ['I', 'love', 'Python', 'programming']
    print(words[2])                  # Python

    # Split by custom separator
    csv_data = "Gagan,22,Delhi,Python"
    parts = csv_data.split(",")
    print(parts)                     # ['Gagan', '22', 'Delhi', 'Python']
    print(parts[0])                  # Gagan
    print(parts[2])                  # Delhi

join() — opposite of split, combines a list into a string:


    words = ["I", "love", "Python"]
    sentence = " ".join(words)       # join with space
    print(sentence)                  # I love Python

    tags = ["python", "coding", "beginner"]
    result = ", ".join(tags)         # join with comma and space
    print(result)                    # python, coding, beginner

    path = ["home", "user", "documents"]
    file_path = "/".join(path)
    print(file_path)                 # home/user/documents


Check Methods


    print("hello".isalpha())      # True  — only letters
    print("hello123".isalpha())   # False — has numbers
    print("12345".isdigit())      # True  — only digits
    print("hello123".isalnum())   # True  — letters and numbers
    print("   ".isspace())        # True  — only whitespace
    print("Hello World".istitle()) # True — title case

Real use case — validating user input:


    age = input("Enter your age: ")

    if age.isdigit():
        age = int(age)
        print(f"Your age is {age}")
    else:
        print("Invalid input — please enter a number")


String Formatting — All Three Ways

You've been using f-strings. Let's see all three ways Python supports:

Way 1 — f-strings (Modern, Recommended)


    name = "Gagan"
    age = 22
    print(f"My name is {name} and I am {age} years old")

You can even put expressions directly inside:


    price = 100
    quantity = 3
    print(f"Total: Rs.{price * quantity}")       # Rs.300
    print(f"Pi is approximately {22/7:.4f}")     # Pi is approximately 3.1429


Way 2 — .format() method (Older)


    name = "Gagan"
    age = 22
    print("My name is {} and I am {} years old".format(name, age))

You'll see this in older code. f-strings are better but good to recognize this.


Way 3 — % operator (Very Old)


    name = "Gagan"
    age = 22
    print("My name is %s and I am %d years old" % (name, age))

Very old style — you'll rarely use this. Just recognize it if you see it.

Stick to f-strings — they're the cleanest and most modern.


f-string Formatting Options

You can control how values are displayed inside f-strings:


    number = 1234567.89

    print(f"{number:.2f}")          # 1234567.89  (2 decimal places)
    print(f"{number:,.2f}")         # 1,234,567.89 (comma separator)
    print(f"{number:.0f}")          # 1234568     (no decimals, rounded)

    # Padding and alignment
    name = "Gagan"
    print(f"{name:10}")             # "Gagan     " (10 chars, left aligned)
    print(f"{name:>10}")            # "     Gagan" (10 chars, right aligned)
    print(f"{name:^10}")            # "  Gagan   " (10 chars, center aligned)

    # Numbers with padding
    for i in range(1, 6):
        print(f"Item {i:02d}")      # Item 01, Item 02... (pad with zeros)

Output of last loop:

Item 01
Item 02
Item 03
Item 04
Item 05

Multiline Strings

Use triple quotes for strings that span multiple lines:


    message = """
    Hello Gagan,

    Welcome to our platform.
    Your account has been created successfully.

    Thank you,
    Team Python
    """

    print(message)

Output:


Hello Gagan,

Welcome to our platform.
Your account has been created successfully.

Thank you,
Team Python


Escape Characters

Special characters inside strings:


    print("Hello\nWorld")         # \n = new line
    print("Hello\tWorld")         # \t = tab space
    print("He said \"Hello\"")    # \" = double quote inside string
    print("C:\\Users\\Gagan")     # \\ = backslash

Output:

Hello
World
Hello	World
He said "Hello"
C:\Users\Gagan

Real World Example — User Registration Validator


    def validate_username(username):
        username = username.strip()
        if len(username) < 3:
            return False, "Username too short (minimum 3 characters)"
        if len(username) > 20:
            return False, "Username too long (maximum 20 characters)"
        if not username.isalnum():
            return False, "Username can only contain letters and numbers"
        return True, "Username is valid"

    def validate_email(email):
        email = email.strip().lower()
        if "@" not in email:
            return False, "Email must contain @"
        if "." not in email:
            return False, "Email must contain a dot"
        return True, "Email is valid"

    def validate_password(password):
        if len(password) < 8:
            return False, "Password too short (minimum 8 characters)"
        if password.isalpha():
            return False, "Password must contain at least one number"
        if password.isdigit():
            return False, "Password must contain at least one letter"
        return True, "Password is valid"


    print("=== User Registration ===")

    username = input("Enter username: ")
    email = input("Enter email: ")
    password = input("Enter password: ")

    valid_u, msg_u = validate_username(username)
    valid_e, msg_e = validate_email(email)
    valid_p, msg_p = validate_password(password)

    print(f"\nUsername: {msg_u}")
    print(f"Email: {msg_e}")
    print(f"Password: {msg_p}")

    if valid_u and valid_e and valid_p:
        print("\nRegistration successful!")
    else:
        print("\nRegistration failed. Please fix the errors above.")

This is close to real production code — input validation is something every developer writes.


Quick Reference — All String Methods


    text = "  Hello World  "

    # Case
    text.upper()            # HELLO WORLD
    text.lower()            # hello world
    text.title()            # Hello World
    text.capitalize()       # Hello world

    # Whitespace
    text.strip()            # "Hello World"
    text.lstrip()           # "Hello World  "
    text.rstrip()           # "  Hello World"

    # Search
    text.find("World")      # index or -1
    text.count("l")         # count occurrences
    text.startswith("  H")  # True/False
    text.endswith("  ")     # True/False
    "Hello" in text         # True/False

    # Modify
    text.replace("World", "Python")
    text.split()            # split into list
    " ".join(list)          # join list into string

    # Check
    text.isalpha()          # only letters?
    text.isdigit()          # only digits?
    text.isalnum()          # letters and numbers?
    text.isspace()          # only whitespace?

    # Length
    len(text)               # total characters


Exercise 🏋️

Build a Text Analyzer program:

  1. Ask user to enter any sentence
  2. Analyze and display:
Enter a sentence: Hello World this is Python programming

=== Text Analysis ===
Original text    : Hello World this is Python programming
Uppercase        : HELLO WORLD THIS IS PYTHON PROGRAMMING
Lowercase        : hello world this is python programming
Total characters : 38
Without spaces   : 32
Total words      : 6
Word list        : ['Hello', 'World', 'this', 'is', 'Python', 'programming']
Starts with 'H'  : True
Ends with 'ing'  : True
Contains 'Python': True
Count of 'o'     : 3
Reversed text    : gnimmargorp nohtyP si siht dlroW olleH

Hints:

  • Count without spaces: text.replace(" ", "")
  • Total words: len(text.split())
  • Reversed text: text[::-1] (slicing with step -1 reverses anything)

Object Oriented Programming (OOP) in Python

Classes and Objects

What is OOP?

So far you've written procedural code — a list of instructions that run one by one, with functions to organize them.

OOP is a completely different way of thinking about code. Instead of writing instructions, you model real world things as objects in your code.


Real World Analogy — The Blueprint

Think of a class as a blueprint and an object as the actual thing built from that blueprint.

Blueprint of a Car (Class)
- Has: color, brand, speed, fuel
- Can: start, stop, accelerate, brake

Actual Car 1 (Object): Red Toyota, speed=0
Actual Car 2 (Object): Blue Honda, speed=60
Actual Car 3 (Object): Black BMW, speed=120

One blueprint — many cars. Each car has the same properties and abilities but different values.

Same in Python:

Student Class (Blueprint)
- Has: name, age, marks
- Can: introduce(), get_grade(), study()

student1 = Student("Rahul", 20, 85)
student2 = Student("Priya", 21, 92)
student3 = Student("Gagan", 22, 78)

Your First Class


    class Dog:
        def __init__(self, name, breed, age):
            self.name = name
            self.breed = breed
            self.age = age

        def bark(self):
            print(f"{self.name} says: Woof!")

        def introduce(self):
            print(f"I am {self.name}, a {self.breed}, {self.age} years old")

Let's break this down piece by piece.


class keyword

class Dog:

class tells Python you're defining a class. Dog is the class name. Convention — class names start with Capital Letter.


__init__ — The Constructor


    def __init__(self, name, breed, age):
        self.name = name
        self.breed = breed
        self.age = age

__init__ is a special method that runs automatically when you create an object. It's called the constructor — it initializes (sets up) the object.

self — refers to the object being created. When you create dog1, self means dog1. When you create dog2, self means dog2.

self.name = name — stores the name parameter as an attribute of the object. Attributes are variables that belong to the object.


Creating Objects


    dog1 = Dog("Bruno", "Labrador", 3)
    dog2 = Dog("Max", "German Shepherd", 5)
    dog3 = Dog("Charlie", "Poodle", 2)

This is called instantiation — creating an instance (object) of a class.

Each object is completely independent with its own data.


Accessing Attributes and Calling Methods


    dog1 = Dog("Bruno", "Labrador", 3)

    # Accessing attributes
    print(dog1.name)       # Bruno
    print(dog1.breed)      # Labrador
    print(dog1.age)        # 3

    # Calling methods
    dog1.bark()            # Bruno says: Woof!
    dog1.introduce()       # I am Bruno, a Labrador, 3 years old


Full Example — All Three Dogs


    class Dog:
        def __init__(self, name, breed, age):
            self.name = name
            self.breed = breed
            self.age = age

        def bark(self):
            print(f"{self.name} says: Woof!")

        def introduce(self):
            print(f"I am {self.name}, a {self.breed}, {self.age} years old")

        def birthday(self):
            self.age += 1
            print(f"Happy Birthday {self.name}! Now {self.age} years old")


    dog1 = Dog("Bruno", "Labrador", 3)
    dog2 = Dog("Max", "German Shepherd", 5)
    dog3 = Dog("Charlie", "Poodle", 2)

    dog1.introduce()
    dog2.introduce()
    dog3.introduce()

    print()

    dog1.bark()
    dog2.bark()

    print()

    dog1.birthday()
    print(f"Bruno's age is now: {dog1.age}")    # 4
    print(f"Max's age is still: {dog2.age}")    # 5 — unchanged

Output:

I am Bruno, a Labrador, 3 years old
I am Max, a German Shepherd, 5 years old
I am Charlie, a Poodle, 2 years old

Bruno says: Woof!
Max says: Woof!

Happy Birthday Bruno! Now 4 years old
Bruno's age is now: 4
Max's age is still: 5

Methods vs Functions

A function is defined outside a class with def:


    def greet(name):
        print(f"Hello {name}")

A method is a function defined inside a class. It always has self as first parameter:


    class Person:
        def greet(self):          # method — belongs to class
            print(f"Hello {self.name}")

They work the same way but methods are tied to a specific object.


self Explained Simply

self is just a reference to the current object. When you call:

dog1.bark()

Python internally does:

Dog.bark(dog1)    # passes dog1 as self

So inside bark(), when you write self.name — it means dog1.name. Simple.

You could technically name it anything but self is the universal convention. Always use self.


Real World Class — Bank Account


    class BankAccount:
        def __init__(self, owner, balance=0):
            self.owner = owner
            self.balance = balance
            self.transactions = []

        def deposit(self, amount):
            if amount <= 0:
                print("Deposit amount must be positive")
                return
            self.balance += amount
            self.transactions.append(f"Deposited: Rs.{amount}")
            print(f"Rs.{amount} deposited. New balance: Rs.{self.balance}")

        def withdraw(self, amount):
            if amount <= 0:
                print("Withdrawal amount must be positive")
                return
            if amount > self.balance:
                print(f"Insufficient balance. Available: Rs.{self.balance}")
                return
            self.balance -= amount
            self.transactions.append(f"Withdrawn: Rs.{amount}")
            print(f"Rs.{amount} withdrawn. New balance: Rs.{self.balance}")

        def get_balance(self):
            print(f"\nAccount: {self.owner}")
            print(f"Balance: Rs.{self.balance}")

        def get_statement(self):
            print(f"\n=== Statement for {self.owner} ===")
            if not self.transactions:
                print("No transactions yet")
            else:
                for t in self.transactions:
                    print(f"  {t}")
            print(f"Current Balance: Rs.{self.balance}")


    # Create two accounts
    acc1 = BankAccount("Gagan", 1000)
    acc2 = BankAccount("Rahul")          # starts with 0

    acc1.deposit(5000)
    acc1.withdraw(2000)
    acc1.withdraw(10000)                  # should fail — insufficient balance
    acc1.get_statement()

    print()

    acc2.deposit(3000)
    acc2.get_balance()

Output:

Rs.5000 deposited. New balance: Rs.6000
Rs.2000 withdrawn. New balance: Rs.4000
Insufficient balance. Available: Rs.4000

=== Statement for Gagan ===
  Deposited: Rs.5000
  Withdrawn: Rs.2000
Current Balance: Rs.4000

Rs.3000 deposited. New balance: Rs.3000

Account: Rahul
Balance: Rs.3000

This is real OOP — data (balance, transactions) and behavior (deposit, withdraw) bundled together in one object.


Class Attributes vs Instance Attributes

Instance attributes — unique to each object (what we've been using):

self.name = name     # each dog has its own name

Class attributes — shared by ALL objects of the class:


    class Dog:
        species = "Canis familiaris"    # class attribute — same for all dogs

        def __init__(self, name):
            self.name = name            # instance attribute — different per dog

    dog1 = Dog("Bruno")
    dog2 = Dog("Max")

    print(dog1.species)     # Canis familiaris
    print(dog2.species)     # Canis familiaris
    print(dog1.name)        # Bruno
    print(dog2.name)        # Max

    print(Dog.species)      # can also access via class name


__str__ — String Representation

When you print an object directly you get something ugly:


    dog1 = Dog("Bruno", "Labrador", 3)
    print(dog1)    # <__main__.Dog object at 0x7f...> — ugly!

Define __str__ to control what prints:


    class Dog:
        def __init__(self, name, breed, age):
            self.name = name
            self.breed = breed
            self.age = age

        def __str__(self):
            return f"Dog({self.name}, {self.breed}, {self.age} years)"

    dog1 = Dog("Bruno", "Labrador", 3)
    print(dog1)    # Dog(Bruno, Labrador, 3 years)

__str__ is called automatically when you use print() or str() on your object. Always define it — makes debugging much easier.


Inheritance — One Class Extending Another

Inheritance lets you create a new class based on an existing one — it inherits all attributes and methods of the parent class.


    # Parent class
    class Animal:
        def __init__(self, name, age):
            self.name = name
            self.age = age

        def eat(self):
            print(f"{self.name} is eating")

        def sleep(self):
            print(f"{self.name} is sleeping")

        def __str__(self):
            return f"{self.name} (age {self.age})"


    # Child class — inherits from Animal
    class Dog(Animal):
        def __init__(self, name, age, breed):
            super().__init__(name, age)    # call parent's __init__
            self.breed = breed             # add extra attribute

        def bark(self):
            print(f"{self.name} says: Woof!")

        def fetch(self):
            print(f"{self.name} fetches the ball!")


    class Cat(Animal):
        def __init__(self, name, age, indoor):
            super().__init__(name, age)
            self.indoor = indoor

        def meow(self):
            print(f"{self.name} says: Meow!")

        def purr(self):
            print(f"{self.name} is purring...")


    dog = Dog("Bruno", 3, "Labrador")
    cat = Cat("Whiskers", 2, True)

    # Dog has Animal methods AND its own methods
    dog.eat()          # from Animal — Bruno is eating
    dog.sleep()        # from Animal — Bruno is sleeping
    dog.bark()         # from Dog — Bruno says: Woof!
    dog.fetch()        # from Dog — Bruno fetches the ball!

    print()

    cat.eat()          # from Animal
    cat.meow()         # from Cat
    cat.purr()         # from Cat

    print(dog)         # Bruno (age 3)
    print(cat)         # Whiskers (age 2)

Output:

Bruno is eating
Bruno is sleeping
Bruno says: Woof!
Bruno fetches the ball!

Whiskers is eating
Whiskers says: Meow!
Whiskers is purring...
Bruno (age 3)
Whiskers (age 2)

super() Explained


    class Dog(Animal):
        def __init__(self, name, age, breed):
            super().__init__(name, age)    # runs Animal's __init__
            self.breed = breed

super() calls the parent class. Without super().__init__(), the name and age attributes from Animal would never get set. Always call super().__init__() in child class constructor.


Overriding Methods

Child class can override (replace) parent's method:


    class Animal:
        def speak(self):
            print("Some sound")

    class Dog(Animal):
        def speak(self):                    # overrides Animal's speak
            print(f"{self.name} says: Woof!")

    class Cat(Animal):
        def speak(self):                    # overrides Animal's speak
            print(f"{self.name} says: Meow!")

    class Duck(Animal):
        def speak(self):                    # overrides Animal's speak
            print(f"{self.name} says: Quack!")

    animals = [Dog("Bruno", 3, "Lab"), Cat("Whiskers", 2, True), Duck("Donald", 4)]

    # Same method call — different behavior based on object type
    # This is called Polymorphism
    for animal in animals:
        animal.speak()

Output:

Bruno says: Woof!
Whiskers says: Meow!
Donald says: Quack!

This is Polymorphism — same method name, different behavior depending on the object. One of the core OOP concepts.


Encapsulation — Protecting Data

Encapsulation means hiding internal data and only exposing what's necessary.

In Python, use _ (single underscore) to indicate "private" — don't access directly:


    class BankAccount:
        def __init__(self, owner, balance):
            self.owner = owner
            self._balance = balance        # _ means "private, don't touch directly"

        def get_balance(self):             # controlled access
            return self._balance

        def deposit(self, amount):
            if amount > 0:
                self._balance += amount    # controlled modification

    acc = BankAccount("Gagan", 1000)

    # Bad practice — accessing private attribute directly
    print(acc._balance)    # works but you shouldn't do this

    # Good practice — use the method
    print(acc.get_balance())    # 1000
    acc.deposit(500)
    print(acc.get_balance())    # 1500

Python doesn't strictly enforce private attributes like some other languages but the _ convention signals to other developers "don't access this directly."


Real World Example — Student Management System with OOP


    class Student:
        student_count = 0    # class attribute — tracks total students

        def __init__(self, name, age, email):
            self.name = name
            self.age = age
            self.email = email
            self.marks = []
            self.student_id = Student.student_count + 1
            Student.student_count += 1

        def add_marks(self, subject, mark):
            self.marks.append({"subject": subject, "mark": mark})

        def get_average(self):
            if not self.marks:
                return 0
            total = sum(m["mark"] for m in self.marks)
            return total / len(self.marks)

        def get_grade(self):
            avg = self.get_average()
            if avg >= 90: return "A"
            elif avg >= 80: return "B"
            elif avg >= 70: return "C"
            elif avg >= 60: return "D"
            else: return "F"

        def print_report(self):
            print(f"\n{'='*35}")
            print(f"  STUDENT REPORT CARD")
            print(f"{'='*35}")
            print(f"  ID     : {self.student_id}")
            print(f"  Name   : {self.name}")
            print(f"  Age    : {self.age}")
            print(f"  Email  : {self.email}")
            print(f"{'='*35}")
            if self.marks:
                for m in self.marks:
                    print(f"  {m['subject']:<15}: {m['mark']}")
                print(f"{'='*35}")
                print(f"  Average : {self.get_average():.2f}")
                print(f"  Grade   : {self.get_grade()}")
            else:
                print("  No marks added yet")
            print(f"{'='*35}")

        def __str__(self):
            return f"Student({self.student_id}: {self.name}, Grade: {self.get_grade()})"


    class Classroom:
        def __init__(self, class_name):
            self.class_name = class_name
            self.students = []

        def add_student(self, student):
            self.students.append(student)
            print(f"{student.name} added to {self.class_name}")

        def find_student(self, name):
            for student in self.students:
                if student.name.lower() == name.lower():
                    return student
            return None

        def get_top_student(self):
            if not self.students:
                return None
            return max(self.students, key=lambda s: s.get_average())

        def print_summary(self):
            print(f"\n=== {self.class_name} Summary ===")
            print(f"Total students: {len(self.students)}")
            for student in self.students:
                print(f"  {student}")


    # Using the system
    classroom = Classroom("Python Batch 2025")

    s1 = Student("Rahul", 20, "rahul@email.com")
    s2 = Student("Priya", 21, "priya@email.com")
    s3 = Student("Gagan", 22, "gagan@email.com")

    classroom.add_student(s1)
    classroom.add_student(s2)
    classroom.add_student(s3)

    s1.add_marks("Python", 85)
    s1.add_marks("Math", 78)
    s1.add_marks("English", 82)

    s2.add_marks("Python", 92)
    s2.add_marks("Math", 95)
    s2.add_marks("English", 88)

    s3.add_marks("Python", 78)
    s3.add_marks("Math", 72)
    s3.add_marks("English", 80)

    s1.print_report()
    s2.print_report()

    classroom.print_summary()

    top = classroom.get_top_student()
    print(f"\nTop student: {top.name} with average {top.get_average():.2f}")
    print(f"Total students enrolled: {Student.student_count}")

Output:

Rahul added to Python Batch 2025
Priya added to Python Batch 2025
Gagan added to Python Batch 2025

===================================
  STUDENT REPORT CARD
===================================
  ID     : 1
  Name   : Rahul
  Age    : 20
  Email  : rahul@email.com
===================================
  Python         : 85
  Math           : 78
  English        : 82
===================================
  Average : 81.67
  Grade   : B
===================================

...

=== Python Batch 2025 Summary ===
Total students: 3
  Student(1: Rahul, Grade: B)
  Student(2: Priya, Grade: A)
  Student(3: Gagan, Grade: C)

Top student: Priya with average 91.67
Total students enrolled: 3

Four Pillars of OOP — Summary

Pillar

Meaning

Example

Encapsulation

Bundle data and methods together, hide internals

_balance in BankAccount

Inheritance

Child class extends parent class

Dog extends Animal

Polymorphism

Same method, different behavior

speak() in Dog, Cat, Duck

Abstraction

Hide complexity, show only what's needed

deposit() hides balance logic


Exercise 🏋️

Build a Library Management System using OOP:

Create these classes:

Book class:

  • Attributes: title, author, isbn, is_available (default True)
  • Methods: __str__, borrow(), return_book()

Member class:

  • Attributes: name, member_id, borrowed_books (list)
  • Methods: borrow_book(book), return_book(book), print_profile()

Library class:

  • Attributes: name, books (list), members (list)
  • Methods: add_book(book), register_member(member), search_book(title), print_catalog()

Requirements:

  • Member cannot borrow a book that's already borrowed
  • Member cannot borrow more than 3 books at once
  • When book is returned, it becomes available again
  • print_catalog() shows all books with availability status

This is a real system — using everything from all 9 stages together!


Pythonic Code & Best Practices

Writing Code the Python Way What is "Pythonic" Code? You can solve any problem in many ways. But Python has a preferred style — ...