Object-Oriented Programming
Use ← → arrow keys or Space to navigate | Press F for fullscreen
Organizing code around objects that combine data and behavior
Data and functions are separate — you pass data to functions and hope nothing breaks.
name = "Lia"
balance = 100.0
def deposit(balance, amount):
return balance + amount
balance = deposit(balance, 50)
Data and behavior are bundled together — the object knows what it can do.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
acc = BankAccount("Lia", 100.0)
acc.deposit(50)
Classes, instances, __init__, methods, properties
self refers to this instance inside the class.type(obj) → the class | isinstance(obj, Cls) → membership check
class Point:
def __init__(self, x, y):
self.x = x # instance attribute
self.y = y
def distance_to(self, other):
return ((self.x - other.x)**2 +
(self.y - other.y)**2) ** 0.5
p1 = Point(0, 0) # instance
p2 = Point(3, 4)
print(p1.distance_to(p2)) # 5.0
print(isinstance(p1, Point)) # True
__init__ Methodself.return anything (implicitly returns None).class BankAccount:
def __init__(self, owner="Unknown",
balance=0.0):
self.owner = owner
self._balance = balance # _ = convention
acc = BankAccount("Lia", 500.0)
print(acc.owner) # Lia
print(acc._balance) # 500.0
print().
== between objects.
<; enables sorting.
+ operator.
len(obj).
in operator.
@property@attr.setter to validate writes._balance signals internal use. It is a convention, not enforcement.
class BankAccount:
def __init__(self, balance=0.0):
self._balance = balance
@property
def balance(self): # getter
return self._balance
@balance.setter
def balance(self, value): # setter
if value < 0:
raise ValueError("Negative balance")
self._balance = value
acc = BankAccount(100)
print(acc.balance) # 100 (no parentheses!)
acc.balance = 200 # calls setter
acc.balance = -5 # raises ValueError
Encapsulation · Polymorphism · Inheritance · Abstraction
Bundle data + behavior together, and restrict direct access to internals.
| Convention | Meaning |
|---|---|
name | Public — use freely |
_name | Internal — avoid outside class |
__name | Name-mangled — strongest hint |
class Thermostat:
def __init__(self, temp):
self._temp = temp # internal state
@property
def temperature(self):
return self._temp
def set_temperature(self, value):
if value < 0 or value > 40:
raise ValueError("Out of range")
self._temp = value
t = Thermostat(22)
t.set_temperature(25) # OK
t.set_temperature(100) # ValueError
super() to call the parent's method.issubclass(Child, Parent) → Trueclass Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def speak(self): # override
return f"{self.name} barks"
class Cat(Animal):
def speak(self): # override
return f"{self.name} meows"
pets = [Dog("Rex"), Cat("Luna")]
for p in pets:
print(p.speak())
# Rex barks
# Luna meows
class Shape:
def area(self): ...
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
shapes = [Circle(5), Rectangle(3, 4)]
for s in shapes:
print(s.area()) # each behaves correctly
TypeError.from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def charge(self, amount):
... # no implementation here
class CreditCard(PaymentMethod):
def charge(self, amount):
print(f"Charging ${amount} to card")
class PayPal(PaymentMethod):
def charge(self, amount):
print(f"Sending ${amount} via PayPal")
# PaymentMethod() ← TypeError!
cc = CreditCard()
cc.charge(50) # Charging $50 to card
super()Shared across all instances of the class. Set on the class, not on self.
class Counter:
count = 0 # class variable
def __init__(self):
Counter.count += 1
a = Counter()
b = Counter()
print(Counter.count) # 2
super()Call the parent class's method — especially __init__ — without hardcoding the parent's name.
class SavingsAccount(BankAccount):
def __init__(self, owner, balance,
interest_rate):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def apply_interest(self):
self._balance *= (1 + self.interest_rate)
Comparison dunders · Operator overloading · @dataclass · Static & class methods
| Method | Operator |
|---|---|
__eq__ | == |
__lt__ | < |
__le__ | <= |
__gt__ | > |
__ge__ | >= |
__eq__ + one of the ordering methods, then decorate with @functools.total_ordering to get the rest automatically.
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, gpa):
self.name = name
self.gpa = gpa
def __eq__(self, other):
return self.gpa == other.gpa
def __lt__(self, other):
return self.gpa < other.gpa
s1 = Student("Alice", 3.8)
s2 = Student("Bob", 3.5)
print(s1 > s2) # True ← derived by decorator
print(sorted([s1, s2], reverse=True)) # by GPA
__hash____eq__, Python sets __hash__ = None (unhashable) by default.__hash__ explicitly — it must be consistent with __eq__.class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
p = Point(1, 2)
seen = {p} # works — hashable
memo = {p: "origin"} # works as dict key
Define dunder methods to make built-in operators work on your objects.
| Method | Operator |
|---|---|
__add__ | + |
__sub__ | - |
__mul__ | * |
__len__ | len() |
__getitem__ | obj[i] |
__contains__ | in |
class Vector:
def __init__(self, x, y):
self.x, self.y = x, 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 __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
@dataclassA decorator that auto-generates __init__, __repr__, and __eq__ from annotated fields — eliminating boilerplate.
| Option | Effect |
|---|---|
eq=True (default) | Auto-generates __eq__ |
order=True | Also generates <, <=, >, >= |
frozen=True | Immutable; enables __hash__ |
field(default_factory=…) | Safe mutable default (e.g. list) |
from dataclasses import dataclass, field
@dataclass(order=True)
class Student:
name: str
gpa: float
courses: list = field(default_factory=list)
s1 = Student("Alice", 3.8)
s2 = Student("Bob", 3.5)
print(s1) # Student(name='Alice', gpa=3.8, ...)
print(s1 > s2) # True (order=True)
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1, 2)
# p.x = 9 ← FrozenInstanceError
d = {p: "origin"} # hashable!
self; unique per object.# WRONG — all instances share one list!
class Broken:
items = [] # class variable
# CORRECT — each instance gets its own list
class Fixed:
def __init__(self):
self.items = [] # instance variable
class Player:
count = 0 # class variable
def __init__(self, name):
self.name = name # instance var
Player.count += 1 # update class var
p1 = Player("Lia")
p2 = Player("Kai")
print(Player.count) # 2 (shared)
print(p1.name) # Lia (unique)
print(p2.name) # Kai (unique)
| Decorator | First param | Typical use |
|---|---|---|
| none | self | Regular method on an instance |
@classmethod | cls | Alternative constructors; access class state |
@staticmethod | — | Utility; logically related but needs no instance or class |
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@classmethod
def from_fahrenheit(cls, f): # alt constructor
return cls((f - 32) * 5/9)
@staticmethod
def is_valid(celsius): # utility
return -273.15 <= celsius
t = Temperature.from_fahrenheit(212)
print(t.celsius) # 100.0
print(Temperature.is_valid(-300)) # False
| Concept | Key syntax / notes |
|---|---|
| Class definition | class Name: + __init__(self, …) |
| Instance creation | obj = Name(args) |
| Property | @property getter; @attr.setter for validation |
| Inheritance | class Child(Parent):; use super().__init__() |
| Abstract methods | from abc import ABC, abstractmethod; @abstractmethod |
| Ordering | __eq__ + __lt__ + @total_ordering |
| Operator overload | __add__, __mul__, __len__, … |
| @dataclass | order=True, frozen=True, field(default_factory=…) |
| Class method | @classmethod + cls param; for alt constructors |
| Static method | @staticmethod; no self or cls |
Next up: Ch13 — Functional Programming
map · filter · reduce · lambda · decorators · context managers