Object-Oriented Basics
Classes
Blueprints for Objects
You're tracking users in your app. Each user has a name, email, and login method. Instead of managing separate dictionaries, a class bundles the data and behavior into one reusable blueprint - cleaner and more maintainable.
Basic class definition
Create a class with attributes and methods.
# Basic Class Definition
print("=== Basic Class ===\n")
# Define a simple class
class Dog:
pass # Empty class for now
# Create an object (instance)
my_dog = Dog()
print(f"Created: {my_dog}")
print(f"Type: {type(my_dog)}")
print("\n=== Class with Attribute ===")
class Cat:
species = "Felis catus" # Class attribute
# Create cats
cat1 = Cat()
cat2 = Cat()
print(f"Cat 1 species: {cat1.species}")
print(f"Cat 2 species: {cat2.species}")
# Both share the same class attribute
print(f"Same attribute? {cat1.species is cat2.species}")
print("\n=== Adding Instance Attributes ===")
# Add attributes to specific instance
cat1.name = "Whiskers"
cat2.name = "Luna"
print(f"Cat 1 name: {cat1.name}")
print(f"Cat 2 name: {cat2.name}")
print("\n=== Class vs Object ===")
print("""
Class = Blueprint/Template
- Defines structure
- Like a cookie cutter
Object = Instance/Reality
- Actual data
- Like actual cookies
""")
# Multiple objects from same class
dog1 = Dog()
dog2 = Dog()
dog3 = Dog()
print(f"dog1 is dog2? {dog1 is dog2}") # False - different objects
print(f"All are Dogs? {type(dog1) == type(dog2) == Dog}") # True
class Name: defines a blueprint. Methods are functions inside the class.
Instance attributes
Each object has its own copy of attributes.
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Instance Attributes with __init__
print("=== The __init__ Method ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Creating dog: {name}, age {age}")
# Create dogs
buddy =
max_dog = Dog("Max", 5)
print(f"\n{buddy.name} is {buddy.age} years old")
print(f"{max_dog.name} is {max_dog.age} years old")
print("\n=== Default Values ===")
class Cat:
def __init__(self, name, color="orange"):
self.name = name
self.color = color
# With and without default
garfield = Cat("Garfield") # Uses default color
whiskers = Cat("Whiskers", "gray")
print(f"{garfield.name} is {garfield.color}")
print(f"{whiskers.name} is {whiskers.color}")
print("\n=== Multiple Initialization Patterns ===")
class Person:
def __init__(self, name, age=0, city="Unknown"):
self.name = name
self.age = age
self.city = city
def display(self):
print(f"{self.name}, {self.age}, from {self.city}")
# Various ways to create
p1 = Person("Alice")
p2 = Person("Bob", 25)
p3 = Person("Carol", 30, "NYC")
p4 = Person("Dave", city="LA")
p1.display()
p2.display()
p3.display()
p4.display()
print("\n=== Computed Attributes ===")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
rect =
print(f"Rectangle {rect.width}x{rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
Attributes set in __init__ are unique to each object.
Instance methods
Methods that operate on object data.
# Instance Methods
print("=== Instance Methods ===\n")
class Dog:
def __init__(self, name, energy=100):
self.name = name
self.energy = energy
def bark(self):
print(f"{self.name} says: Woof! Woof!")
def play(self):
if self.energy >= 20:
self.energy -= 20
print(f"{self.name} plays! Energy: {self.energy}")
else:
print(f"{self.name} is too tired to play")
def rest(self):
self.energy = min(100, self.energy + 30)
print(f"{self.name} rests. Energy: {self.energy}")
def status(self):
return f"{self.name}: {self.energy}/100 energy"
# Use methods
buddy = Dog("Buddy")
buddy.bark()
print(buddy.status())
print("\n=== Method Chaining State ===")
buddy.play()
buddy.play()
buddy.play()
buddy.play() # Energy check
buddy.rest()
print(buddy.status())
print("\n=== Methods Returning Values ===")
class Calculator:
def __init__(self, initial=0):
self.value = initial
def add(self, n):
self.value += n
return self.value
def multiply(self, n):
self.value *= n
return self.value
def reset(self):
self.value = 0
return self.value
calc = Calculator(10)
print(f"Start: {calc.value}")
print(f"Add 5: {calc.add(5)}")
print(f"Multiply by 3: {calc.multiply(3)}")
print(f"Current: {calc.value}")
print("\n=== Methods Calling Other Methods ===")
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
self._log_transaction("deposit", amount)
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
self._log_transaction("withdraw", amount)
return True
return False
def _log_transaction(self, type, amount):
print(f"[LOG] {self.owner}: {type} ${amount}, balance ${self.balance}")
account = BankAccount("Alice", 100)
account.deposit(50)
account.withdraw(30)
Methods take self as first parameter to access the object's attributes.
Multiple objects
Each object is independent with its own data.
# Working with Multiple Objects
print("=== Multiple Objects ===\n")
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self, hours):
improvement = hours * 2
self.grade = min(100, self.grade + improvement)
def display(self):
print(f"{self.name}: {self.grade}/100")
# Create multiple students
alice = Student("Alice", 85)
bob = Student("Bob", 72)
carol = Student("Carol", 90)
# Each has own state
print("Initial grades:")
alice.display()
bob.display()
carol.display()
print("\n=== Objects Act Independently ===")
# Changes to one don't affect others
bob.study(5) # Bob studies
carol.study(3)
print("\nAfter studying:")
alice.display() # Unchanged
bob.display() # Improved by 10
carol.display() # Improved by 6
print("\n=== Objects in a List ===")
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def apply_discount(self, percent):
self.price *= (1 - percent/100)
# Store objects in list
products = [
Product("Laptop", 999),
Product("Phone", 699),
Product("Tablet", 499)
]
# Process all objects
print("Original prices:")
for p in products:
print(f" {p.name}: ${p.price}")
# Apply discount to all
for p in products:
p.apply_discount(10)
print("\nAfter 10% discount:")
for p in products:
print(f" {p.name}: ${p.price:.2f}")
print("\n=== Objects Interacting ===")
class Player:
def __init__(self, name, health=100):
self.name = name
self.health = health
def attack(self, other, damage):
other.health -= damage
print(f"{self.name} attacks {other.name} for {damage} damage!")
print(f" {other.name}'s health: {other.health}")
def is_alive(self):
return self.health > 0
# Two players interact
hero = Player("Hero")
monster = Player("Dragon", 150)
hero.attack(monster, 30)
monster.attack(hero, 25)
hero.attack(monster, 40)
print(f"\nFinal: Hero={hero.health}, Dragon={monster.health}")
Changes to one object don't affect others. Each has its own memory.
Classes with business logic
Methods that compute and transform data.
# Classes with Business Logic
print("=== Shopping Cart ===\n")
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, name, price, quantity=1):
item = {"name": name, "price": price, "qty": quantity}
self.items.append(item)
print(f"Added {quantity}x {name} @ ${price}")
def remove_item(self, name):
for item in self.items:
if item["name"] == name:
self.items.remove(item)
print(f"Removed {name}")
return True
print(f"{name} not found")
return False
def get_total(self):
total = 0
for item in self.items:
total += item["price"] * item["qty"]
return total
def display(self):
print("\n--- Cart Contents ---")
for item in self.items:
subtotal = item["price"] * item["qty"]
print(f" {item['name']}: {item['qty']} x ${item['price']} = ${subtotal}")
print(f"Total: ${self.get_total():.2f}")
# Use the cart
cart = ShoppingCart()
cart.add_item("Apple", 0.50, 5)
cart.add_item("Bread", 2.50)
cart.add_item("Milk", 3.00, 2)
cart.display()
cart.remove_item("Bread")
cart.display()
print("\n=== Temperature Converter ===")
class Temperature:
def __init__(self, celsius=0):
self.celsius = celsius
@property
def fahrenheit(self):
return self.celsius * 9/5 + 32
@property
def kelvin(self):
return self.celsius + 273.15
def set_fahrenheit(self, f):
self.celsius = (f - 32) * 5/9
def describe(self):
if self.celsius < 0:
return "Freezing"
elif self.celsius < 15:
return "Cold"
elif self.celsius < 25:
return "Comfortable"
else:
return "Hot"
temp = Temperature(25)
print(f"{temp.celsius}°C = {temp.fahrenheit}°F = {temp.kelvin}K")
print(f"Feeling: {temp.describe()}")
temp.set_fahrenheit(32) # Set from Fahrenheit
print(f"\n32°F = {temp.celsius:.1f}°C ({temp.describe()})")
print("\n=== Counter with History ===")
class Counter:
def __init__(self, start=0):
self.value = start
self.history = [start]
def increment(self, amount=1):
self.value += amount
self.history.append(self.value)
def decrement(self, amount=1):
self.value -= amount
self.history.append(self.value)
def undo(self):
if len(self.history) > 1:
self.history.pop()
self.value = self.history[-1]
counter = Counter()
counter.increment(5)
counter.increment(3)
counter.decrement(2)
print(f"Value: {counter.value}")
print(f"History: {counter.history}")
counter.undo()
print(f"After undo: {counter.value}")
print(f"History: {counter.history}")
Classes can encapsulate complex logic, not just data storage.
Exercise: practical.py
Build a complete class with attributes, methods, and logic