Classes and Objects: Your First OOP Steps
Welcome to Classes and Objects! Think of a class as a blueprint for a house, and objects as the actual houses built from that blueprint. Let’s start building!
What Are Classes and Objects?
The Blueprint Analogy
# Class = Blueprint
class House:
def __init__(self, color, rooms):
self.color = color
self.rooms = rooms
# Objects = Actual houses
blue_house = House("blue", 3) # One house
red_house = House("red", 4) # Another house
green_house = House("green", 2) # Third house
print(f"Blue house: {blue_house.color} with {blue_house.rooms} rooms")
print(f"Red house: {red_house.color} with {red_house.rooms} rooms")
Real-World Example: Users
class User:
"""A simple User class."""
def __init__(self, name, email):
self.name = name
self.email = email
def greet(self):
return f"Hello, I'm {self.name}!"
# Create user objects
alice = User("Alice", "alice@example.com")
bob = User("Bob", "bob@example.com")
print(alice.greet()) # "Hello, I'm Alice!"
print(bob.greet()) # "Hello, I'm Bob!"
print(alice.name) # "Alice"
print(bob.email) # "bob@example.com"
The __init__ Method
Constructor Basics
class Car:
def __init__(self, make, model, year):
"""Initialize a new Car object."""
self.make = make # Instance variable
self.model = model # Instance variable
self.year = year # Instance variable
self.mileage = 0 # Default value
# Create car objects
car1 = Car("Toyota", "Camry", 2020)
car2 = Car("Honda", "Civic", 2019)
print(f"{car1.year} {car1.make} {car1.model}") # "2020 Toyota Camry"
print(f"Mileage: {car1.mileage}") # "Mileage: 0"
Default Parameters
class Book:
def __init__(self, title, author, pages=100, genre="Fiction"):
self.title = title
self.author = author
self.pages = pages
self.genre = genre
self.is_read = False
# Create books with different parameters
book1 = Book("1984", "George Orwell", 328, "Dystopian")
book2 = Book("The Hobbit", "J.R.R. Tolkien") # Uses defaults
print(f"{book1.title} by {book1.author} - {book1.pages} pages")
print(f"{book2.title} by {book2.author} - {book2.pages} pages, {book2.genre}")
Instance Methods
Methods That Work with Object Data
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
"""Add money to the account."""
if amount > 0:
self.balance += amount
print(f"Deposited ${amount}. New balance: ${self.balance}")
else:
print("Deposit amount must be positive")
def withdraw(self, amount):
"""Remove money from the account."""
if amount > 0 and amount <= self.balance:
self.balance -= amount
print(f"Withdrew ${amount}. New balance: ${self.balance}")
elif amount > self.balance:
print("Insufficient funds")
else:
print("Withdrawal amount must be positive")
def get_balance(self):
"""Return current balance."""
return self.balance
# Create and use a bank account
account = BankAccount("Alice", 1000)
account.deposit(500) # Deposited $500. New balance: $1500
account.withdraw(200) # Withdrew $200. New balance: $1300
account.withdraw(2000) # Insufficient funds
print(f"Final balance: ${account.get_balance()}")
Methods Calling Other Methods
class EmailClient:
def __init__(self, username):
self.username = username
self.inbox = []
self.sent = []
def send_email(self, to, subject, body):
"""Send an email."""
email = {
"to": to,
"subject": subject,
"body": body,
"from": self.username
}
self.sent.append(email)
print(f"Email sent to {to}: {subject}")
# Simulate sending (in real app, this would connect to server)
self._log_activity(f"Sent email to {to}")
def receive_email(self, from_addr, subject, body):
"""Receive an email."""
email = {
"from": from_addr,
"subject": subject,
"body": body
}
self.inbox.append(email)
print(f"Email received from {from_addr}: {subject}")
self._log_activity(f"Received email from {from_addr}")
def _log_activity(self, message):
"""Private method to log activity."""
print(f"[LOG] {self.username}: {message}")
# Test email client
client = EmailClient("alice@example.com")
client.send_email("bob@example.com", "Hello!", "How are you?")
client.receive_email("charlie@example.com", "Meeting", "See you tomorrow")
The self Parameter
Why self Matters
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
def get_count(self):
return self.count
# Without self - THIS WON'T WORK
class BadCounter:
def __init__(self):
count = 0 # This creates a local variable, not an instance variable
def increment(self):
count += 1 # This also creates a local variable
def get_count(self):
return count # NameError - count not defined
# Test
good_counter = Counter()
good_counter.increment()
good_counter.increment()
print(good_counter.get_count()) # 2
bad_counter = BadCounter()
# bad_counter.increment() # Would cause UnboundLocalError
self in Different Contexts
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, I'm {self.name}"
def introduce(self, other_person):
return f"{self.name} meets {other_person.name}"
# Create people
alice = Person("Alice")
bob = Person("Bob")
print(alice.greet()) # "Hello, I'm Alice"
print(bob.greet()) # "Hello, I'm Bob"
print(alice.introduce(bob)) # "Alice meets Bob"
print(bob.introduce(alice)) # "Bob meets Alice"
Class vs Instance Variables
Instance Variables (Unique to Each Object)
class Student:
def __init__(self, name, grade):
self.name = name # Instance variable
self.grade = grade # Instance variable
self.scores = [] # Instance variable
def add_score(self, score):
self.scores.append(score)
# Each student has their own data
alice = Student("Alice", "10th")
bob = Student("Bob", "10th")
alice.add_score(95)
alice.add_score(87)
bob.add_score(92)
print(f"Alice's scores: {alice.scores}") # [95, 87]
print(f"Bob's scores: {bob.scores}") # [92]
Class Variables (Shared by All Objects)
class Student:
school_name = "Python High School" # Class variable
total_students = 0 # Class variable
def __init__(self, name, grade):
self.name = name
self.grade = grade
Student.total_students += 1 # Modify class variable
# All students share the same school
alice = Student("Alice", "10th")
bob = Student("Bob", "11th")
print(alice.school_name) # "Python High School"
print(bob.school_name) # "Python High School"
print(Student.total_students) # 2
# Changing class variable affects all instances
Student.school_name = "Advanced Python Academy"
print(alice.school_name) # "Advanced Python Academy"
print(bob.school_name) # "Advanced Python Academy"
Practical Examples
Example 1: Todo List
class TodoList:
def __init__(self, owner):
self.owner = owner
self.tasks = []
def add_task(self, description, priority="medium"):
"""Add a new task."""
task = {
"description": description,
"priority": priority,
"completed": False
}
self.tasks.append(task)
print(f"Added task: {description}")
def complete_task(self, index):
"""Mark a task as completed."""
if 0 <= index < len(self.tasks):
self.tasks[index]["completed"] = True
print(f"Completed: {self.tasks[index]['description']}")
else:
print("Invalid task index")
def show_tasks(self):
"""Display all tasks."""
if not self.tasks:
print("No tasks in the list")
return
print(f"\n{self.owner}'s Todo List:")
for i, task in enumerate(self.tasks):
status = "✓" if task["completed"] else "☐"
print(f"{i}. {status} {task['description']} ({task['priority']})")
def get_pending_tasks(self):
"""Return count of pending tasks."""
return sum(1 for task in self.tasks if not task["completed"])
# Test todo list
my_todos = TodoList("Alice")
my_todos.add_task("Learn Python classes", "high")
my_todos.add_task("Practice OOP", "medium")
my_todos.add_task("Build a project", "low")
my_todos.show_tasks()
my_todos.complete_task(0)
my_todos.complete_task(1)
print(f"\nPending tasks: {my_todos.get_pending_tasks()}")
my_todos.show_tasks()
Example 2: Simple Game Character
class GameCharacter:
def __init__(self, name, health=100, attack_power=10):
self.name = name
self.health = health
self.max_health = health
self.attack_power = attack_power
self.level = 1
self.experience = 0
def attack(self, target):
"""Attack another character."""
damage = self.attack_power
target.take_damage(damage)
print(f"{self.name} attacks {target.name} for {damage} damage!")
def take_damage(self, damage):
"""Take damage from an attack."""
self.health -= damage
if self.health <= 0:
self.health = 0
print(f"{self.name} has been defeated!")
else:
print(f"{self.name} has {self.health} health remaining")
def heal(self, amount):
"""Heal the character."""
old_health = self.health
self.health = min(self.max_health, self.health + amount)
healed = self.health - old_health
print(f"{self.name} healed for {healed} health")
def gain_experience(self, exp):
"""Gain experience and level up if needed."""
self.experience += exp
print(f"{self.name} gained {exp} experience!")
# Level up every 100 experience
while self.experience >= self.level * 100:
self.level_up()
def level_up(self):
"""Level up the character."""
self.level += 1
self.max_health += 20
self.health = self.max_health # Full heal on level up
self.attack_power += 5
print(f"{self.name} leveled up to level {self.level}!")
def __str__(self):
"""String representation of the character."""
return f"{self.name} (Level {self.level}) - HP: {self.health}/{self.max_health}"
# Test game characters
hero = GameCharacter("Hero", 120, 15)
goblin = GameCharacter("Goblin", 80, 8)
print(hero)
print(goblin)
print()
# Combat
hero.attack(goblin)
goblin.attack(hero)
hero.heal(30)
hero.gain_experience(150) # Should level up
print()
print(hero)
print(goblin)
Example 3: Library Book System
class Book:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self.is_available = True
self.borrower = None
def borrow(self, borrower_name):
"""Borrow the book."""
if self.is_available:
self.is_available = False
self.borrower = borrower_name
print(f"'{self.title}' borrowed by {borrower_name}")
return True
else:
print(f"'{self.title}' is already borrowed by {self.borrower}")
return False
def return_book(self):
"""Return the book."""
if not self.is_available:
print(f"'{self.title}' returned by {self.borrower}")
self.is_available = True
self.borrower = None
return True
else:
print(f"'{self.title}' is not currently borrowed")
return False
def __str__(self):
status = "Available" if self.is_available else f"Borrowed by {self.borrower}"
return f"'{self.title}' by {self.author} - {status}"
class Library:
def __init__(self, name):
self.name = name
self.books = []
def add_book(self, book):
"""Add a book to the library."""
self.books.append(book)
print(f"Added '{book.title}' to {self.name}")
def find_book(self, title):
"""Find a book by title."""
for book in self.books:
if book.title.lower() == title.lower():
return book
return None
def show_available_books(self):
"""Show all available books."""
available = [book for book in self.books if book.is_available]
if available:
print(f"\nAvailable books at {self.name}:")
for book in available:
print(f" - {book}")
else:
print(f"No books available at {self.name}")
# Test library system
library = Library("City Library")
# Add books
book1 = Book("1984", "George Orwell", "123456")
book2 = Book("To Kill a Mockingbird", "Harper Lee", "234567")
book3 = Book("The Great Gatsby", "F. Scott Fitzgerald", "345678")
library.add_book(book1)
library.add_book(book2)
library.add_book(book3)
# Borrow and return books
book1.borrow("Alice")
book2.borrow("Bob")
book1.borrow("Charlie") # Should fail
library.show_available_books()
book1.return_book()
library.show_available_books()
Practice Exercises
Exercise 1: Student Grade System
Create a Student class that:
- Stores name, student ID, and grades
- Has methods to add grades, calculate average
- Can determine if student is passing (average >= 70)
Exercise 2: Bank Account Manager
Build a BankAccount class with:
- Deposit, withdraw, and check balance methods
- Account number and owner name
- Transaction history
- Overdraft protection
Exercise 3: Simple Shopping Cart
Create a shopping cart system with:
Productclass (name, price, quantity)ShoppingCartclass (add items, remove items, calculate total)- Apply discount functionality
Exercise 4: Digital Library
Build a digital library with:
EBookclass (title, author, file size)Libraryclass (add books, search by author/title, track downloads)- Reading progress tracking
Exercise 5: Game Inventory System
Create a game inventory with:
Itemclass (name, description, value)Inventoryclass (add/remove items, calculate total value)- Equipment system (equip/unequip items)
Summary
Classes and objects are the foundation of OOP:
Class Definition:
class ClassName:
def __init__(self, parameters):
self.attribute = value
Creating Objects:
object = ClassName(arguments)
Instance Methods:
def method_name(self, parameters):
# Method body
return result
Key Concepts:
__init__is the constructorselfrefers to the current object- Instance variables are unique to each object
- Class variables are shared by all objects
- Methods define object behavior
Next: Encapsulation - protecting your object’s data! 🔒