bigonotes
CS ConceptsOOP

Abstraction

How abstraction hides implementation behind a contract, and how Python's ABC and @abstractmethod enforce it.

3 min readLast updated OOPListen · Watch on NotebookLM

Abstraction means hiding complexity and exposing only what callers need. The caller knows what an object can do, not how it does it. When you drive, you press the accelerator; you don't manage fuel injection or the transmission.

Abstraction vs encapsulation#

These two are easy to mix up.

Encapsulation Abstraction
Hides Data and internal state Complexity and implementation
Focus How data is protected Which operations are available
Python tools Private attributes, @property ABC, @abstractmethod
Question it answers "How is the data protected?" "What can this object do?"
ATM analogy Your balance is hidden Only deposit/withdraw buttons are shown

In short: encapsulation says "you can't touch the data directly"; abstraction says "you don't need to know how it's done".

The problem it solves#

Without a contract, nothing stops someone from writing an incomplete class. You only find out when it crashes:

class CreditCard:
    pass  # forgot to implement pay()

c = CreditCard()
c.pay(100)  # AttributeError at runtime

Abstraction lets you enforce a contract so the mistake surfaces as soon as someone tries to create the object.

Abstract base classes in Python#

Python's abc module lets you declare methods that every subclass must implement:

from abc import ABC, abstractmethod

class Payment(ABC):          # ABC marks this as an abstract class
    @abstractmethod
    def pay(self, amount):   # every concrete subclass must implement this
        pass

Three cases to know:

p = Payment()        # TypeError: can't instantiate abstract class

class CreditCard(Payment):
    pass

c = CreditCard()     # TypeError: CreditCard doesn't implement pay()

class CreditCard(Payment):
    def pay(self, amount):
        print(f"Paying {amount} via Credit Card")

c = CreditCard()
c.pay(100)           # Paying 100 via Credit Card

Example: a payment system#

Each payment method hides its own processing behind one shared pay method:

from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

    @abstractmethod
    def refund(self, amount):
        pass

class CreditCard(Payment):
    def pay(self, amount):
        print(f"Processing credit card payment of {amount}")

    def refund(self, amount):
        print(f"Refunding {amount} to credit card")

class PayPal(Payment):
    def pay(self, amount):
        print(f"Processing PayPal payment of {amount}")

    def refund(self, amount):
        print(f"Refunding {amount} to PayPal")

class Crypto(Payment):
    def pay(self, amount):
        print(f"Processing crypto payment of {amount}")

    def refund(self, amount):
        print(f"Refunding {amount} to crypto wallet")

# The caller only knows the Payment contract, not how each one works
for p in [CreditCard(), PayPal(), Crypto()]:
    p.pay(100)
Processing credit card payment of 100
Processing PayPal payment of 100
Processing crypto payment of 100

Abstract classes can have concrete methods#

An abstract class can mix required methods with shared, ready-made ones:

class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

    def print_receipt(self, amount):  # inherited as-is
        print(f"Receipt: Payment of {amount} processed")

class CreditCard(Payment):
    def pay(self, amount):
        print(f"Paying {amount} via Credit Card")

c = CreditCard()
c.pay(100)
c.print_receipt(100)

Example: shapes with abstraction and encapsulation#

This example combines an abstract contract with private, validated state:

import math
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Circle(Shape):
    def __init__(self, color, radius):
        self.__color = color   # encapsulated
        self.radius = radius

    @property
    def color(self):
        return self.__color

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius

class Square(Shape):
    def __init__(self, color, size):
        self.__color = color
        self.size = size

    @property
    def color(self):
        return self.__color

    def area(self):
        return self.size * self.size

    def perimeter(self):
        return 4 * self.size

c = Circle("Red", 5)
print(round(c.area(), 2), round(c.perimeter(), 2), c.color)  # 78.54 31.42 Red
s = Square("Blue", 4)
print(s.area(), s.perimeter(), s.color)                      # 16 16 Blue

Where it shows up in LLD#

You almost always reach for abstraction when:

  • Several classes share a common interface (Payment, Vehicle, Notifier).
  • You want to enforce a contract across all implementations.
  • You want polymorphism: treat different objects the same way through one type.

Key takeaways#

  • Abstraction = hide the implementation, expose an interface.
  • Use ABC and @abstractmethod to enforce a contract in Python.
  • Abstract classes can't be instantiated; neither can subclasses that miss an abstract method.
  • Abstract classes may also provide concrete, shared methods.
  • Abstraction is what makes polymorphism useful.

Found a mistake? Tell me on LinkedIn ↗

Further reading#