Skip to content
beginner Phase 2 · Python OOP

Classes & Objects

Define classes, create objects, and understand __init__ and self.

1h 15m
0 problems
Topic Progress 0%

Class Basics

Defining a Class

class Dog:
    # Class variable (shared by all instances)
    species = 'Canis familiaris'
    
    def __init__(self, name, age):
        # Instance variables (unique to each instance)
        self.name = name
        self.age = age
    
    def bark(self):
        return f'{self.name} says Woof!'
    
    def __str__(self):
        return f'{self.name} is {self.age} years old'

# Creating instances
dog1 = Dog('Buddy', 3)
dog2 = Dog('Max', 5)

print(dog1)  # Buddy is 3 years old
print(dog1.bark())  # Buddy says Woof!
print(dog1.species)  # Canis familiaris

The self Parameter

class Point:
    def __init__(self, x, y):
        self.x = x  # 'self' distinguishes instance from local
        self.y = y
    
    def distance_to(self, other):
        # 'self' is the calling instance
        # 'other' is another Point instance
        return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5

p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2))  # 5.0

Class vs Instance Variables

class Employee:
    # Class variable (shared)
    raise_amount = 1.05  # 5% raise
    employee_count = 0
    
    def __init__(self, name, salary):
        self.name = name        # Instance variable
        self.salary = salary    # Instance variable
        Employee.employee_count += 1
    
    def apply_raise(self):
        self.salary *= self.raise_amount

emp1 = Employee('Alice', 50000)
emp2 = Employee('Bob', 60000)

# Instance takes precedence
emp1.raise_amount = 1.10  # Only affects emp1
print(emp1.raise_amount)  # 1.10
print(emp2.raise_amount)  # 1.05 (class variable)
print(Employee.raise_amount)  # 1.05

Methods

Instance Methods

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)
    
    def is_square(self):
        return self.width == self.height

Class Methods

class Date:
    def __init__(self, month, day, year):
        self.month = month
        self.day = day
        self.year = year
    
    @classmethod
    def from_string(cls, date_string):
        # 'cls' is the class itself (like 'self' but for class)
        month, day, year = map(int, date_string.split('-'))
        return cls(month, day, year)
    
    def __str__(self):
        return f'{self.month}/{self.day}/{self.year}'

# Regular instantiation
date1 = Date(12, 25, 2024)

# Class method as alternative constructor
date2 = Date.from_string('12-25-2024')
print(date2)  # 12/25/2024

Static Methods

class MathUtils:
    @staticmethod
    def add(a, b):
        # No 'self' or 'cls' parameter
        # Just a regular function that belongs to the class
        return a + b
    
    @staticmethod
    def is_even(n):
        return n % 2 == 0

# Call without instance
print(MathUtils.add(5, 3))  # 8
print(MathUtils.is_even(4))  # True

Method Types Summary

class MyClass:
    class_var = 'shared'
    
    def instance_method(self):
        # Access: self.class_var, self.instance_var
        # Called on: instance
        pass
    
    @classmethod
    def class_method(cls):
        # Access: cls.class_var
        # Called on: class or instance
        pass
    
    @staticmethod
    def static_method():
        # Access: nothing class-specific
        # Called on: class or instance
        pass

# Usage
obj = MyClass()
obj.instance_method()    # ✅
# MyClass.instance_method()  # ❌ TypeError

MyClass.class_method()   # ✅
obj.class_method()       # ✅

MyClass.static_method()  # ✅
obj.static_method()      # ✅

Object Identity & Equality

Identity vs Equality

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

p1 = Person('Alice', 30)
p2 = Person('Alice', 30)
p3 = p1

# Identity (is) - compares memory address
p1 is p2   # False (different objects)
p1 is p3   # True (same object)

# Equality (==) - compares values
p1 == p2   # False (default: compares identity)
p1 == p3   # True (same object)

Implementing eq

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __eq__(self, other):
        if not isinstance(other, Person):
            return False
        return self.name == other.name and self.age == other.age
    
    def __hash__(self):
        return hash((self.name, self.age))

p1 = Person('Alice', 30)
p2 = Person('Alice', 30)
p3 = Person('Bob', 25)

p1 == p2   # True
p1 == p3   # False

# Now works in sets and dicts
people = {p1, p2, p3}
print(len(people))  # 2 (p1 and p2 are equal)

repr vs str

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __repr__(self):
        # Unambiguous representation (for developers)
        return f'Point({self.x}, {self.y})'
    
    def __str__(self):
        # Readable representation (for users)
        return f'({self.x}, {self.y})'

p = Point(1, 2)
print(repr(p))  # Point(1, 2)
print(str(p))   # (1, 2)
print(p)        # (1, 2) - uses __str__

Common Dunder Methods

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __repr__(self):
        return f'Vector({self.x}, {self.y})'
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __abs__(self):
        return (self.x**2 + self.y**2)**0.5
    
    def __len__(self):
        return 2  # 2D vector
    
    def __bool__(self):
        return self.x != 0 or self.y != 0

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2)   # Vector(4, 6)
print(v1 * 3)    # Vector(3, 6)
print(abs(v1))   # 2.236...
print(len(v1))   # 2
print(bool(v1))  # True