Object Oriented Programming
Abstraction & Abstract Base Classes
A car's steering wheel is an ABSTRACTION:
Jr Codex Python Notes
Level: Advanced Prerequisites: Chapter 5 Time to complete: ~25 minutes
Table of Contents
- What is Abstraction?
- The Informal Approach — And Why It's Not Enough
- The
abcModule @abstractmethodin Practice- Abstract Classes Can Still Share Code
- Abstract Properties
- Abstraction vs Encapsulation — Don't Confuse Them
- Summary & Next Steps
1. What is Abstraction?
Abstraction means defining what a class must do, without necessarily saying how — hiding implementation complexity behind a simple, guaranteed interface. Chapter 3's Shape class was already an early, informal example of this idea.
Abstraction in Everyday Terms
─────────────────────────────────────────
A car's steering wheel is an ABSTRACTION:
You know turning it left turns the car left.
You do NOT need to know the steering rack, tie rods,
or power steering pump mechanics underneath.
─────────────────────────────────────────
In OOP, abstraction usually means: define a base class that specifies required methods (the contract), and guarantee that every subclass implements them — without providing (or caring about) the actual implementation in the base class itself.
2. The Informal Approach — And Why It's Not Enough
Chapter 3 used this pattern:
class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement area()")
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
# oops — forgot to implement area()!
rect = Rectangle(4, 5)
print(rect.area()) # NotImplementedError — but only discovered at RUNTIME,
# only if this exact line ever gets calledThe problem: nothing stops you from instantiating an incomplete subclass, or from forgetting to implement area() entirely — the mistake only surfaces the moment someone happens to call the missing method, which could be much later, in production. Python's abc module fixes this by enforcing the contract at class-definition time.
3. The abc Module
ABC (Abstract Base Class) and @abstractmethod, from Python's built-in abc module, turn "please implement this" into "you literally cannot create this object until you do."
from abc import ABC, abstractmethod
class Shape(ABC): # inherits from ABC — marks this as abstract
@abstractmethod
def area(self):
"""Every subclass MUST implement this."""
pass
@abstractmethod
def perimeter(self):
pass
shape = Shape() # TypeError: Can't instantiate abstract class Shape
# with abstract methods area, perimeterYou cannot create a Shape() directly at all — Python refuses, right at the point of instantiation, long before any specific method gets called.
4. @abstractmethod in Practice
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# missing perimeter() on purpose, to show what happens
rect = Rectangle(4, 5)
# TypeError: Can't instantiate abstract class Rectangle
# with abstract method perimeterclass Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self): # now BOTH abstract methods are implemented
return 2 * (self.width + self.height)
rect = Rectangle(4, 5) # ✓ works now — the contract is fully satisfied
print(rect.area()) # 20
print(rect.perimeter()) # 18The key guarantee abc provides: if a subclass is missing any @abstractmethod, Python refuses to let you create an instance of it — you find out immediately, at the exact moment you try, not buried deep in a later runtime call.
5. Abstract Classes Can Still Share Code
An abstract base class isn't limited to empty method stubs — it can define regular, fully-working methods too. Subclasses only must implement the ones marked @abstractmethod; everything else is inherited normally (Chapter 2).
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
def describe(self):
"""A CONCRETE method — shared by every subclass automatically, no override required."""
return f"{type(self).__name__} has an area of {self.area():.2f}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
circle = Circle(5)
print(circle.describe()) # "Circle has an area of 78.54" — inherited, uses area() polymorphically6. Abstract Properties
@property (Chapter 5) and @abstractmethod combine to enforce that a subclass must provide a specific property, not just a method:
from abc import ABC, abstractmethod
class Employee(ABC):
@property
@abstractmethod
def salary(self):
"""Every subclass must define how salary is computed."""
pass
class SalariedEmployee(Employee):
def __init__(self, annual_salary):
self.annual_salary = annual_salary
@property
def salary(self):
return self.annual_salary / 12
class HourlyEmployee(Employee):
def __init__(self, hourly_rate, hours_worked):
self.hourly_rate = hourly_rate
self.hours_worked = hours_worked
@property
def salary(self):
return self.hourly_rate * self.hours_worked
emp1 = SalariedEmployee(60000)
emp2 = HourlyEmployee(25, 160)
print(emp1.salary) # 5000.0
print(emp2.salary) # 40007. Abstraction vs Encapsulation — Don't Confuse Them
These two terms get mixed up constantly — they solve different problems:
| Encapsulation (Chapter 5) | Abstraction (this chapter) | |
|---|---|---|
| Question it answers | "How do I protect an object's internal data from invalid changes?" | "What must a class guarantee it can do, regardless of how?" |
| Mechanism | _protected, __private, @property | ABC, @abstractmethod |
| Direction | Looking inward — hiding an object's own state | Looking outward — defining a contract other code can rely on |
Both Working Together
─────────────────────────────────────────
class Shape(ABC): ← ABSTRACTION: guarantees every
@abstractmethod Shape has an .area() method
def area(self): pass
class Circle(Shape):
def __init__(self, radius):
self.__radius = radius ← ENCAPSULATION: protects radius
from becoming an invalid value
@property
def radius(self):
return self.__radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self.__radius = value
def area(self):
return 3.14159 * self.__radius ** 2
─────────────────────────────────────────
8. Summary & Next Steps
Key Takeaways
- Abstraction defines a required contract (which methods/properties a class must have) without dictating implementation — Python enforces this via the
abcmodule. class Shape(ABC):plus@abstractmethodprevents instantiating any subclass that hasn't implemented every required method — caught immediately, not at some later runtime call.- An abstract base class can still provide fully working, shared (concrete) methods alongside its abstract ones.
- Abstraction (the outward contract) and encapsulation (inward data protection) are different concerns that often combine in the same class.
Concept Check
- What error do you get if you try to instantiate a subclass that's missing an
@abstractmethodimplementation, and when does that error occur? - Can an abstract base class define a fully-implemented, non-abstract method? Give an example.
- In one sentence, how does abstraction differ from encapsulation?
Next Chapter
→ Chapter 7: Dunder Methods & Operator Overloading
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index