Python Classes and Objects

Master object-oriented programming in Python - from basic concepts to advanced techniques

🏗️ What are Classes and Objects?

In Python, everything is an object. Classes are blueprints for creating objects, providing a means of bundling data and functionality together. Think of a class as a cookie cutter and objects as the actual cookies made with it.


# Creating a simple class and object
class Car:
    def __init__(self, brand):
        self.brand = brand

# Creating an object from the class
my_car = Car("Toyota")
print(my_car.brand)  # Output: Toyota
                                    
OOP
Programming
Reusable
Code
Organized
Structure

Creating Your First Class

Let's start with the simplest possible class to understand the basic syntax:

Simple Class Definition
# Step 1: Define a simple class
class MyClass:
    x = 5  # Class attribute

# Step 2: Create an object (instance)
my_object = MyClass()

# Step 3: Access the attribute
print(my_object.x)  # Output: 5
print(type(my_object))  # Output: 

The __init__() Constructor

The __init__() method is called automatically when creating an object. It's used to initialize the object's attributes:

Constructor Example
class Person:
    def __init__(self, name, age):
        self.name = name  # Instance attribute
        self.age = age    # Instance attribute
    
    def introduce(self):
        return f"Hi, I'm {self.name} and I'm {self.age} years old"

# Create objects with different data
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)

print(person1.introduce())  # Hi, I'm Alice and I'm 25 years old
print(person2.introduce())  # Hi, I'm Bob and I'm 30 years old

Object Methods

Methods are functions that belong to objects. They can access and modify the object's data:

Methods Example
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance
    
    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            return f"Deposited ${amount}. New balance: ${self.balance}"
        return "Invalid amount"
    
    def withdraw(self, amount):
        if amount > 0 and amount <= self.balance:
            self.balance -= amount
            return f"Withdrew ${amount}. New balance: ${self.balance}"
        return "Insufficient funds or invalid amount"
    
    def get_balance(self):
        return f"Current balance: ${self.balance}"

# Using the class
account = BankAccount("John", 100)
print(account.get_balance())  # Current balance: $100
print(account.deposit(50))    # Deposited $50. New balance: $150
print(account.withdraw(30))   # Withdrew $30. New balance: $120

Understanding the 'self' Parameter

The self parameter refers to the current instance of the class. It's not a keyword but a convention:

Self Parameter Explained
class Car:
    def __init__(myself, brand, model):  # 'myself' instead of 'self'
        myself.brand = brand
        myself.model = model
        myself.speed = 0
    
    def accelerate(this_car, increase):  # 'this_car' instead of 'self'
        this_car.speed += increase
        return f"{this_car.brand} {this_car.model} is now going {this_car.speed} mph"

# The first parameter name doesn't matter, but 'self' is the convention
car = Car("Toyota", "Camry")
print(car.accelerate(30))  # Toyota Camry is now going 30 mph

# You can also call methods this way (not recommended):
print(Car.accelerate(car, 20))  # Toyota Camry is now going 50 mph

Modifying and Deleting Object Properties

You can modify, add, or delete object properties dynamically:

Property Management
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

student = Student("Emma", "A")
print(f"Original: {student.name}, Grade: {student.grade}")

# Modify existing property
student.grade = "A+"
print(f"Modified grade: {student.grade}")

# Add new property
student.age = 20
print(f"Added age: {student.age}")

# Delete property
del student.age
# print(student.age)  # This would raise AttributeError

# Delete entire object
# del student
# print(student)  # This would raise NameError

Real-World Example: Library Management

Let's create a more complex example that demonstrates multiple concepts:

Library Management System
class Book:
    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn
        self.is_available = True
        self.borrowed_by = None
    
    def borrow(self, borrower_name):
        if self.is_available:
            self.is_available = False
            self.borrowed_by = borrower_name
            return f"'{self.title}' borrowed by {borrower_name}"
        return f"'{self.title}' is not available"
    
    def return_book(self):
        if not self.is_available:
            borrower = self.borrowed_by
            self.is_available = True
            self.borrowed_by = None
            return f"'{self.title}' returned by {borrower}"
        return f"'{self.title}' was not borrowed"
    
    def get_info(self):
        status = "Available" if self.is_available else f"Borrowed by {self.borrowed_by}"
        return f"'{self.title}' by {self.author} - {status}"

class Library:
    def __init__(self, name):
        self.name = name
        self.books = []
    
    def add_book(self, book):
        self.books.append(book)
        return f"Added '{book.title}' to {self.name}"
    
    def find_book(self, title):
        for book in self.books:
            if book.title.lower() == title.lower():
                return book
        return None
    
    def list_available_books(self):
        available = [book for book in self.books if book.is_available]
        if available:
            return [book.get_info() for book in available]
        return ["No books available"]

# Using the library system
library = Library("City Library")

# Create books
book1 = Book("Python Programming", "John Smith", "123456789")
book2 = Book("Data Science Basics", "Jane Doe", "987654321")

# Add books to library
print(library.add_book(book1))
print(library.add_book(book2))

# Borrow a book
print(book1.borrow("Alice"))

# Check available books
print("\nAvailable books:")
for info in library.list_available_books():
    print(f"- {info}")

# Return the book
print(f"\n{book1.return_book()}")

# Check available books again
print("\nAvailable books after return:")
for info in library.list_available_books():
    print(f"- {info}")

The pass Statement

Use pass when you need a class definition but haven't implemented it yet:

Using pass Statement
# Empty class placeholder
class FutureFeature:
    pass  # TODO: Implement later

# You can still create objects
placeholder = FutureFeature()
print(type(placeholder))  # 

# Add attributes dynamically
placeholder.name = "Test"
placeholder.value = 42
print(f"Name: {placeholder.name}, Value: {placeholder.value}")