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