SythraOpen app

Pillar Three: Abstraction in Python

Abstraction means hiding a class's complicated internal logic behind a small, simple set of methods. Python's abc module lets you define abstract classes with @abstractmethod, creating a contract that subclasses must fulfill before they can be instantiated.

Sythra

10 min read

XLinkedIn
Pillar Three: Abstraction in Python — cover illustration

Back in The Four Pillars of OOP's coffee machine story, abstraction was the idea of pressing one button labeled "Brew," without needing to know anything about the heating element, the pump pressure, or the internal wiring. All that complexity is hidden from you — you're given a simple, clean way to get your coffee.

This article is about doing exactly that in code — designing classes so people using them only ever need to know a small, simple set of methods, while everything complicated happens quietly behind the scenes. You'll also meet abstract classes, which let you define a "contract" other classes must follow, without writing the actual logic yourself.

What you will learn

  • Why you've already been benefiting from abstraction, every time you called .sort() or len()
  • How to design your own methods so using them is simple, even if the internals are complicated
  • How to define a contract with Python's abc module and @abstractmethod
  • Why abstract classes catch missing-implementation bugs immediately
  • The real difference between abstraction and encapsulation

You've already been using abstraction

You've been benefiting from abstraction this entire course, every time you used a built-in Python feature:

numbers = [5, 3, 8, 1, 9]
numbers.sort()

When you call .sort(), do you know exactly which sorting algorithm Python uses internally, or how it compares elements? Almost certainly not — and that's fine, because you were never supposed to need to know. You just needed to know: "calling .sort() puts my list in order." Everything else is hidden on purpose. That's abstraction, working exactly as intended — the same is true of len(), print(), range(), and almost every built-in tool you've used.

Designing your own classes with abstraction in mind

Now let's flip this around — instead of just benefiting from abstraction, let's design something with it on purpose. Here's a method on Time that calculates how much time has passed between two Time objects:

class Time:
    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second

    def time_to_int(self):
        minutes = self.hour * 60 + self.minute
        seconds = minutes * 60 + self.second
        return seconds

    def time_difference(self, other):
        """Returns the number of seconds between two times."""
        return abs(self.time_to_int() - other.time_to_int())

If you're using this Time class in your own program, do you need to understand that internally it converts everything into total seconds and subtracts? No. You just need to know one thing:

t1 = Time(14, 30, 0)
t2 = Time(15, 0, 0)
print(t1.time_difference(t2))
# → 1800

Call time_difference, hand it another Time, get back a number of seconds — that's the entire "interface" you need. The mechanics are the implementation, and you never need to look at it. This is the heart of abstraction: design your methods so using them is simple, even if what happens inside is genuinely complicated.

A real example: hiding a messy implementation

Here's a class managing a to-do list, where items secretly need to stay sorted by priority behind the scenes:

class TodoList:
    def __init__(self):
        self._tasks = []   # protected — internal storage, sorted by priority

    def add_task(self, description, priority):
        """Adds a new task. Keeps the internal list sorted automatically."""
        self._tasks.append((priority, description))
        self._tasks.sort(reverse=True)   # highest priority first

    def get_next_task(self):
        """Returns the description of the highest-priority task."""
        if not self._tasks:
            return None
        return self._tasks[0][1]

    def complete_next_task(self):
        """Removes and returns the highest-priority task."""
        if not self._tasks:
            return None
        return self._tasks.pop(0)[1]
todo = TodoList()
todo.add_task("Wash dishes", 2)
todo.add_task("Finish homework", 5)
todo.add_task("Walk the dog", 3)

print(todo.get_next_task())      # Finish homework
print(todo.complete_next_task()) # Finish homework
print(todo.get_next_task())      # Walk the dog

Whoever uses TodoList never needed to know tasks are stored as (priority, description) tuples, or that the list gets re-sorted every time something is added. All of that messy implementation is tucked safely behind three simple, clearly-named methods. If you later rewrite the internals to use a proper priority queue, nothing about how add_task is called needs to change — the interface stays stable while the implementation is free to change.

Abstract classes: defining a contract without writing the logic

So far, abstraction has hidden details inside a class. Python's abstract classes let you define a "contract" — specifying what methods a class must have, without writing how they should work. Imagine you're handling many shapes — circles, squares, triangles — and every shape needs to calculate its own area. You want to guarantee every shape class has an area() method, but the formula differs per shape. Python's abc module (Abstract Base Classes) handles exactly this:

from abc import ABC, abstractmethod

class Shape(ABC):
    """An abstract base class — defines a contract, but no implementation."""

    @abstractmethod
    def area(self):
        """Every shape must know how to calculate its own area."""
        pass

    @abstractmethod
    def perimeter(self):
        """Every shape must know how to calculate its own perimeter."""
        pass

Shape inherits from ABC, and its methods are marked @abstractmethod. This means you cannot create a Shape object directly, ever:

s = Shape()
# → TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter

Shape isn't meant to represent any actual shape — it's a template describing what any real shape must be able to do. Now build real shapes that fulfill the contract:

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14159 * self.radius


class Rectangle(Shape):
    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)

Because both fulfill the entire contract, Python happily lets you create them:

c = Circle(5)
print(c.area())        # 78.53975

r = Rectangle(4, 6)
print(r.area())        # 24

But what if you forget a required method?

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height
    # Oops — forgot to write perimeter()!

t = Triangle(3, 4)
# → TypeError: Can't instantiate abstract class Triangle with abstract method perimeter

Python catches this immediately, the moment you try to create a Triangle — not later, buried deep in your program when you accidentally call .perimeter() and it doesn't exist.

Why bother with abstract classes?

  • A guaranteed, enforced contract — without it, someone could write a Triangle that forgets area() entirely, and your program crashes later, far from the actual mistake.
  • Code that works with "any shape" — since every shape guarantees an area() method, one function can work correctly with any of them (this is polymorphism, explored next).
  • Communicating intent clearly — an abstract class documents in code that it's a blueprint, not something meant to be used directly.
def print_total_area(shapes):
    total = 0
    for shape in shapes:
        total += shape.area()   # works no matter what kind of shape this is!
    print("Total area:", total)

shapes = [Circle(5), Rectangle(4, 6)]
print_total_area(shapes)
# → Total area: 102.53975

print_total_area doesn't know or care whether each shape is a Circle or a Rectangle — it just trusts the contract, since every shape is required to inherit from Shape.

Abstraction vs. encapsulation: what's the difference?

Encapsulation is about protecting an object's internal data — making sure it can never be set to an invalid value, typically via properties, getters, and setters. It's concerned with safety.

Abstraction is about simplifying what someone needs to know to use something — hiding complicated logic behind a small, clean set of methods. It's concerned with simplicity.

Common mistakes

  • Wrapping a single, standalone class in an abstract base class with no siblings sharing a contract — there's nothing to abstract away yet
  • Forgetting to implement every @abstractmethod in a subclass, then being surprised by a TypeError at instantiation
  • Confusing abstraction (hiding complexity) with encapsulation (protecting data) — they're related but solve different problems
  • Exposing internal implementation details in a method's name or return type, defeating the purpose of hiding them

Common questions

What is abstraction in Python?

Abstraction is designing a class so that using it is simple — exposing a small set of clearly-named methods — while hiding the complicated logic that makes those methods work. The user only needs to know the interface, not the implementation.

What is an abstract class in Python?

An abstract class, created by inheriting from ABC (from the abc module), defines methods marked with @abstractmethod that subclasses are required to implement. The abstract class itself cannot be instantiated directly.

What happens if a subclass doesn't implement an abstract method?

Python raises a TypeError immediately when you try to create an instance of that subclass, listing exactly which abstract methods are still missing — catching the mistake right away instead of letting it cause a confusing crash later.

What is the difference between abstraction and encapsulation?

Encapsulation protects an object's data from being set to invalid values, typically using properties and validation. Abstraction hides complicated logic behind a simple interface, so users don't need to understand how something works internally to use it correctly.

Explore

Related topics

Keep going — these sit next to this concept in a real learning path.

Browse all python explainers →