Object-oriented programming organizes code into objects that bundle data (what an object has) with behavior (what it does). This recap pulls the four pillars together in Python, then covers the one idea the earlier lessons only touched: interfaces. Each pillar has its own deeper lesson linked below.
Classes and objects#
A class is the blueprint; an object is an instance made from it.
class Watch:
def __init__(self, brand: str, hour: int, minute: int):
self.brand = brand # attributes: what it has
self.hour = hour
self.minute = minute
def show_time(self) -> None: # behavior: what it does
print(f"Time is {self.hour}:{self.minute:02d}")
my_watch = Watch("Casio", 10, 30)
my_watch.show_time() # Time is 10:30
The four pillars#
A handy mnemonic is PIE + A (or "A PIE"): Abstraction, Polymorphism, Inheritance, Encapsulation.
Encapsulation#
Hide internal state and control access to it. Like a phone's circuitry sealed in a case, you only use the buttons. Deep dive: Encapsulation.
class Watch:
def __init__(self):
self._hour = 0 # internal: change it through set_time
self._minute = 0
def set_time(self, h: int, m: int) -> None:
# Validation lives with the data, so no caller can set 25:99
if 0 <= h <= 23 and 0 <= m <= 59:
self._hour, self._minute = h, m
else:
print("Invalid time!")
Watch().set_time(25, 99) # Invalid time!
Inheritance#
Build new classes from existing ones by naming a parent class in the class statement. The child gets the parent's members plus its own. Deep dive: Inheritance.
class Vehicle:
def move(self) -> None:
print("Vehicle is moving")
class Car(Vehicle):
def honk(self) -> None:
print("Beep beep!")
# A Car can move() (inherited) and honk() (its own),
# but it can't use a sibling class's methods.
car = Car()
car.move()
car.honk()
Polymorphism#
"Many forms": the same call behaves differently per object. In Python this is overriding a parent's method, plus duck typing: any object with the right method works. Deep dive: Polymorphism.
class Animal:
def make_sound(self) -> None:
print("Some sound")
class Dog(Animal):
def make_sound(self) -> None:
print("Woof!")
class Cat(Animal):
def make_sound(self) -> None:
print("Meow!")
for a in [Dog(), Cat()]:
a.make_sound() # Woof! then Meow!: decided by the actual object
Abstraction#
Expose what an object does, hide how. An abstract base class declares methods without bodies that subclasses must implement, and you can't instantiate it. Deep dive: Abstraction.
import math
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self) -> float: # subclasses must provide it
...
def display(self) -> None: # concrete, shared
print("This is a shape")
class Circle(Shape):
def __init__(self, radius: float):
self._radius = radius
def calculate_area(self) -> float:
return math.pi * self._radius ** 2
print(round(Circle(1).calculate_area(), 2)) # 3.14
# Shape() raises TypeError: can't instantiate an abstract class
Interfaces#
An interface is a contract of abilities: a list of methods a class promises to provide. Python has no interface keyword; you get the same thing two ways:
- An ABC with only abstract methods: classes inherit from it explicitly, and Python refuses to instantiate a subclass that misses a method.
- A
typing.Protocol: any class with matching methods satisfies it, without inheriting from it (structural typing, checked by type checkers). - A class can inherit from several ABCs, so it can promise many abilities.
from typing import Final, Protocol
class Playable(Protocol):
def play(self) -> None: ...
def pause(self) -> None: ...
def stop(self) -> None: ...
MAX_VOLUME: Final = 100 # constants live at module or class level
class MusicPlayer: # no inheritance needed: it just has the methods
def play(self) -> None:
print("Playing")
def pause(self) -> None:
print("Paused")
def stop(self) -> None:
print("Stopped")
def start(device: Playable) -> None:
device.play()
start(MusicPlayer()) # Playing
Abstract base class vs protocol#
Both describe a contract; they differ in how a class signs up for it.
Abstract base class (ABC) |
Protocol |
|---|---|
| Mix of abstract and concrete methods | Just method signatures |
Can have instance state and __init__ |
No state of its own |
| Classes must inherit from it | Any class with the right methods fits |
| "Partly built, finish me" | "Here are the abilities you must have" |
| Use for closely related classes sharing code | Use for a capability unrelated classes share |
All four together#
One small hierarchy shows every pillar at once:
from abc import ABC, abstractmethod
# Abstraction: an incomplete template
class Employee(ABC):
def __init__(self, name: str):
self._name = name # Encapsulation: internal state...
@property
def name(self) -> str: # ...exposed through a read-only accessor
return self._name
@abstractmethod
def calculate_salary(self) -> float:
...
# Inheritance: Manager is an Employee
class Manager(Employee):
def __init__(self, name: str, base_salary: float, bonus: float):
super().__init__(name)
self._base_salary = base_salary
self._bonus = bonus
# Polymorphism: each Employee type computes salary its own way
def calculate_salary(self) -> float:
return self._base_salary + self._bonus
m = Manager("Ana", 100_000, 20_000)
print(m.name, m.calculate_salary()) # Ana 120000
Key terms#
These are the words you'll hear in every OOP discussion:
| Term | Meaning |
|---|---|
| Class / object | Blueprint / instance |
| Attribute (field, property) | What an object has |
| Method (behavior) | What an object does |
| Encapsulation | Hiding and protecting data |
| Inheritance | Child classes reuse parent features |
| Polymorphism | Same call, different behavior |
| Abstraction | Hiding complexity behind a contract |
| Interface | A contract of required methods (ABC or Protocol) |
| Override | Replacing a parent's method in a child |
Key takeaways#
- Objects bundle state and behavior; classes are their blueprints.
- Encapsulation protects data, inheritance reuses code, polymorphism varies behavior, abstraction hides complexity.
- Abstract base classes share code among related types; protocols define capabilities any class can adopt.
- A Python class can inherit from several bases, so it can sign up for many interfaces.