Web Analytics

Abstract Classes

Intermediate ~25 min read

Abstract classes define interfaces that subclasses must implement. Using Python's abc module, you can create classes that cannot be instantiated directly and enforce that child classes implement specific methods. This is Python's way of creating formal contracts between classes.

What Are Abstract Classes?

  • Abstract class: A class that cannot be instantiated directly
  • Abstract method: A method with no implementation that must be overridden
  • Concrete method: A regular method with implementation (can be inherited)
  • Subclasses must implement ALL abstract methods to be instantiable

Abstract Class Basics

Inherit from ABC and use @abstractmethod to define methods that subclasses must implement. Trying to instantiate an incomplete class raises TypeError.

Output
Click Run to execute your code

Concrete Methods in Abstract Classes

Abstract classes can have concrete (implemented) methods too. These are inherited normally. Use this for shared functionality that all subclasses need, while still enforcing that specific methods must be customized.

Abstract Properties

You can also define abstract properties that subclasses must implement. Stack @property and @abstractmethod decorators (property first).

Output
Click Run to execute your code

Decorator Order Matters

When combining decorators, @property must come before @abstractmethod:

@property
@abstractmethod
def name(self):
    pass

Practical Examples

Abstract classes are ideal for defining interfaces in plugin systems, database drivers, payment processors, and other extensible systems.

Output
Click Run to execute your code

Template Method Pattern

Abstract classes enable the Template Method pattern: define the skeleton of an algorithm in a concrete method, calling abstract methods that subclasses customize. The abstract class controls the flow while subclasses provide specific behavior.

Abstract Classes vs Duck Typing

Python offers both explicit interfaces (ABC) and implicit interfaces (duck typing). Understanding when to use each is key to Pythonic design.

Output
Click Run to execute your code

typing.Protocol (Python 3.8+)

For the best of both worlds, consider Protocol from the typing module. It provides structural subtyping (duck typing) with type checker support - no inheritance required, but still documents the expected interface.

Common Mistakes

1. Forgetting to inherit from ABC

# Wrong - @abstractmethod does nothing without ABC!
class Shape:
    @abstractmethod
    def area(self):
        pass

s = Shape()  # Works! No enforcement

# Correct - must inherit from ABC
from abc import ABC, abstractmethod

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

s = Shape()  # TypeError! Can't instantiate

2. Wrong decorator order for abstract properties

# Wrong order!
class Vehicle(ABC):
    @abstractmethod
    @property
    def speed(self):  # This won't work correctly
        pass

# Correct - @property first, then @abstractmethod
class Vehicle(ABC):
    @property
    @abstractmethod
    def speed(self):
        pass

3. Not implementing all abstract methods

class Animal(ABC):
    @abstractmethod
    def speak(self): pass

    @abstractmethod
    def move(self): pass

# Incomplete - missing move()
class Dog(Animal):
    def speak(self):
        return "Woof"

d = Dog()  # TypeError: Can't instantiate - move() not implemented

# Complete - both methods implemented
class Dog(Animal):
    def speak(self):
        return "Woof"

    def move(self):
        return "Run"

4. Calling super() on abstract methods that raise NotImplementedError

# Problem - abstract method raises error
class Base(ABC):
    @abstractmethod
    def process(self):
        raise NotImplementedError

class Child(Base):
    def process(self):
        super().process()  # This raises NotImplementedError!
        return "processed"

# Better - abstract method with pass or documentation
class Base(ABC):
    @abstractmethod
    def process(self):
        """Process the data. Override in subclass."""
        pass  # or just docstring

5. Using ABC when duck typing would suffice

# Over-engineered - simple interface doesn't need ABC
class Printable(ABC):
    @abstractmethod
    def to_string(self): pass

# Pythonic - just document the expected interface
def print_item(item):
    """Print item. Item must have to_string() method."""
    print(item.to_string())

# ABC is best for complex interfaces, frameworks, or team codebases

Exercise: Notification System

Task: Create an abstract notification system with multiple notification types.

Requirements:

  • Abstract Notifier class with abstract send() and name property
  • Concrete notify() method that prints "[name] Sending..." then calls send()
  • EmailNotifier, SMSNotifier, PushNotifier implementations
  • notify_all() function to send to multiple notifiers
Output
Click Run to execute your code
Show Solution
from abc import ABC, abstractmethod

class Notifier(ABC):
    @property
    @abstractmethod
    def name(self):
        pass

    @abstractmethod
    def send(self, message):
        pass

    def notify(self, message):
        print(f"[{self.name}] Sending...")
        result = self.send(message)
        print(result)


class EmailNotifier(Notifier):
    def __init__(self, email_address):
        self.email_address = email_address

    @property
    def name(self):
        return "Email"

    def send(self, message):
        return f"Email sent to {self.email_address}: {message}"


class SMSNotifier(Notifier):
    def __init__(self, phone_number):
        self.phone_number = phone_number

    @property
    def name(self):
        return "SMS"

    def send(self, message):
        return f"SMS sent to {self.phone_number}: {message}"


class PushNotifier(Notifier):
    def __init__(self, device_id):
        self.device_id = device_id

    @property
    def name(self):
        return "Push"

    def send(self, message):
        return f"Push notification to device {self.device_id}: {message}"


def notify_all(notifiers, message):
    for notifier in notifiers:
        notifier.notify(message)


# Test
notifiers = [
    EmailNotifier("[email protected]"),
    SMSNotifier("+1234567890"),
    PushNotifier("device-abc-123")
]
notify_all(notifiers, "Your order has shipped!")

Summary

  • from abc import ABC, abstractmethod to use abstract classes
  • Inherit from ABC to make a class abstract
  • @abstractmethod marks methods that must be overridden
  • Can't instantiate abstract classes or classes with unimplemented abstract methods
  • Abstract classes can have concrete methods (template method pattern)
  • Use @property before @abstractmethod for abstract properties
  • Consider typing.Protocol for structural (duck) typing with type hints

What's Next?

Now that you understand abstract classes, learn about Dataclasses - Python's @dataclass decorator that automatically generates __init__, __repr__, __eq__, and more for data-holding classes.