Object-Oriented Programming
OOP organizes code around objects โ bundles of data (attributes) and behavior (methods). Python is fully object-oriented, but it doesn't force you to use it โ use OOP when it makes code clearer.
class Dog:
"""Represents a dog with a name and breed."""
species = "Canis lupus familiaris" # Class attribute (shared by all instances)
def __init__(self, name, breed, age):
"""Constructor โ called when creating an instance."""
self.name = name # Instance attributes
self.breed = breed
self.age = age
def bark(self):
return f"{self.name} says: Woof!"
def birthday(self):
self.age += 1
return f"Happy birthday {self.name}! Now {self.age}."
def describe(self):
return f"{self.name} is a {self.age}-year-old {self.breed}"
# Create instances
rex = Dog("Rex", "German Shepherd", 3)
fluffy = Dog("Fluffy", "Poodle", 5)
print(rex.bark()) # Rex says: Woof!
print(fluffy.describe()) # Fluffy is a 5-year-old Poodle
print(rex.species) # Canis lupus familiaris (class attribute)
rex.birthday() # rex.age becomes 4The self Parameter
Every instance method takes self as its first parameter โ a reference to the instance being acted on. Python passes it automatically when you call rex.bark().
Class vs Instance Attributes
class Counter:
count = 0 # Class attribute โ shared by ALL instances
def __init__(self):
self.id = Counter.count # Instance gets unique ID
Counter.count += 1 # Modify class attribute via class name
c1 = Counter() # Counter.count = 1
c2 = Counter() # Counter.count = 2
c3 = Counter() # Counter.count = 3
print(c1.id) # 0
print(c2.id) # 1
print(Counter.count) # 3Magic Methods (Dunder Methods)
Magic methods (double-underscore methods, "dunders") let your classes work with Python's built-in operators and functions.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# String representation
def __repr__(self):
return f"Vector({self.x}, {self.y})" # For developers
def __str__(self):
return f"({self.x}, {self.y})" # For users/print()
# Arithmetic operators
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# Comparison
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __abs__(self):
return (self.x**2 + self.y**2) ** 0.5
def __len__(self):
return 2 # A 2D vector has 2 components
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v1 * 3) # (3, 6)
print(abs(v2)) # 5.0
print(v1 == v1) # True
print(repr(v1)) # Vector(1, 2)Useful Dunder Methods
| Method | Triggered by |
|---|---|
__init__ | ClassName(args) โ constructor |
__repr__ | repr(obj), REPL display |
__str__ | str(obj), print(obj) |
__len__ | len(obj) |
__getitem__ | obj[key] |
__contains__ | x in obj |
__iter__ | for x in obj |
__enter__/__exit__ | with obj as x |
Properties and Class Methods
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius # Convention: _ prefix = "private"
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@classmethod
def from_fahrenheit(cls, f):
return cls((f - 32) * 5 / 9)
@staticmethod
def is_valid(value):
return value >= -273.15
t = Temperature(100)
print(t.celsius) # 100
print(t.fahrenheit) # 212.0
t2 = Temperature.from_fahrenheit(32)
print(t2.celsius) # 0.0
Temperature.is_valid(-300) # FalseKey Takeaways
- __init__ is the constructor: sets up instance attributes
- self is the instance: all instance methods receive it as first arg
- Magic methods enable operator overloading: make your objects feel native
- @property: computed attributes with optional validation
- @classmethod vs @staticmethod: classmethod gets the class, staticmethod gets neither
Practice Exercises
- Create a
BankAccountclass with deposit, withdraw (raise ValueError if insufficient funds), and a propertybalance. Log all transactions. - Create a
Fractionclass that supports +, -, *, / operators and reduces fractions automatically using GCD. - Create a
Stackclass backed by a list that supports push, pop, peek, is_empty, and __len__. - Add
__iter__to your Stack class so you can use it in a for loop (iterates from top to bottom).