RSSAmplifier

Python Friday · Aug 7, 2026

#343: Abstract Base Classes

0
Sign in to vote or save

info@PythonFriday.dev (Johnny Graber) · pythonfriday.dev

advanced

When we design a group of related classes, we often want to guarantee that every member provides the same set of methods. We could write that expectation into the documentation and hope for the best, or we could let Python enforce it for us with the abc module and its abstract base classes.

Why should we use abstract base classes?

An abstract base class defines a contract: it lists the methods a subclass must implement, without providing the implementation itself. If a subclass forgets one of them, Python refuses to create an instance and tells us why.

That moves the error from somewhere deep in production to the moment we try to use the class. It also documents the intent of our design in code, which is far harder to ignore than a comment.

Import abc

The abc module is part of the standard library, so there is nothing to install:

from abc import ABC, abstractmethod

Create an abstract base class

We inherit from ABC and mark the required methods with the @abstractmethod decorator:

class Notifier(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        pass

Notifier now describes what a notifier does, but it cannot do anything itself. Trying to instantiate it fails:

Notifier()
# TypeError: Can't instantiate abstract class Notifier
# without an implementation for abstract method 'send'

Implement the contract

A subclass becomes usable as soon as it implements every abstract method:

class EmailNotifier(Notifier):
    def send(self, message: str) -> None:
        print(f"=> Email: {message}")

EmailNotifier().send("The build failed")
# Output: => Email: The build failed

If we forget, we get the same error as before - this time for our own class:

class SmsNotifier(Notifier):
    pass

SmsNotifier()
# TypeError: Can't instantiate abstract class SmsNotifier
# without an implementation for abstract method 'send'

Mix abstract and concrete methods

An abstract base class is not limited to empty methods. We can add regular methods that build on the abstract ones:

class Notifier(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        pass

    def send_all(self, messages: list[str]) -> None:
        for message in messages:
            self.send(message)

Every subclass gets send_all for free, while send stays its own responsibility.

Declare abstract properties

Attributes can be part of the contract too, by combining @property with @abstractmethod. The order matters: @property goes on top, @abstractmethod directly above the method.

from abc import ABC, abstractmethod

class Notifier(ABC):
    @property
    @abstractmethod
    def channel(self) -> str:
        """The name of the channel this notifier writes to."""

    @abstractmethod
    def send(self, message: str) -> None:
        pass

    def announce(self, message: str) -> None:
        print(f"[{self.channel}] {message}")

A subclass can satisfy channel with a property of its own:

class EmailNotifier(Notifier):
    @property
    def channel(self) -> str:
        return "email"

    def send(self, message: str) -> None:
        print(f"Sending mail: {message}")

EmailNotifier().announce("Deployment finished")
# Output: [email] Deployment finished

EmailNotifier().channel
# Output: 'email'

Python does not insist on a property, though, it only checks that the name is no longer abstract. A plain class attribute works just as well, which keeps simple subclasses short:

class SmsNotifier(Notifier):
    channel = "sms"

    def send(self, message: str) -> None:
        print(f"Sending SMS: {message}")

SmsNotifier().announce("Deployment finished")
# Output: [sms] Deployment finished

An instance attribute assigned in __init__ is accepted too:

class WebhookNotifier(Notifier):
    def __init__(self, url: str) -> None:
        self.channel = url

    def send(self, message: str) -> None:
        print(f"POST {self.channel}: {message}")

And if we implement send but forget channel, we run into the familiar error before the class is ever used:

class SlackNotifier(Notifier):
    def send(self, message: str) -> None:
        print(f"Slack: {message}")

SlackNotifier()
# TypeError: Can't instantiate abstract class SlackNotifier
# without an implementation for abstract method 'channel'

The same combination works for @classmethod, @staticmethod and property setters, so we can put any part of a class into the contract.

Conclusion

Abstract base classes give us a cheap way to turn an informal agreement between classes into something Python checks for us. They cost a single decorator and a base class, and in return we get clear intent, early failures, and helpful error messages. Whenever we find ourselves writing "every subclass has to implement this", abc is the tool we are looking for.

Read the original on pythonfriday.dev

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.