🚀AdvancedAdvanced Patterns

Abstract Base Classes

abc.ABC, @abstractmethod, virtual subclasses, and using ABCs for interface enforcement.

Overview

Python's `abc` module provides **Abstract Base Classes (ABCs)** a way to define interfaces that subclasses must implement. If a subclass doesn't override all `@abstractmethod` methods, instantiating it raises `TypeError`. This catches implementation errors early, at instantiation time rather than when the missing method is first called. ABCs also support **virtual subclasses** via `ABC.register(SomeClass)` or by implementing `__subclasshook__`. This lets you declare that a class "conforms to" an interface without inheriting from it the basis of Python's duck-typing-friendly structural subtyping used in `collections.abc` (Sequence, Mapping, Iterable, etc.). The standard library's `collections.abc` module is rich with ABCs you can inherit from or test against: `Iterable`, `Iterator`, `Sequence`, `Mapping`, `MutableMapping`, `Callable`, `Hashable`, and more. Checking `isinstance(obj, collections.abc.Mapping)` works for any dict-like object, even custom ones.

Key Points

  • •Inherit from `abc.ABC` (or set `metaclass=ABCMeta`) to create an abstract class
  • •`@abc.abstractmethod` marks a method as required in all concrete subclasses
  • •Trying to instantiate a class with unimplemented abstract methods raises `TypeError`
  • •`@abc.abstractproperty` is deprecated use `@property` combined with `@abstractmethod`
  • •`ABC.register(Class)` makes Class a virtual subclass (isinstance-compatible) without real inheritance
  • •`__subclasshook__(cls, C)` override for custom isinstance logic based on duck typing
  • •`collections.abc` provides ready-made ABCs: Iterable, Iterator, Sequence, Mapping, Callable, etc.

Examples

Abstract base class enforcing interface

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

    def describe(self):
        print(f"{type(self).__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}")

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        import math; return math.pi * self.r ** 2
    def perimeter(self):
        import math; return 2 * math.pi * self.r

class Square(Shape):
    def __init__(self, s):
        self.s = s
    def area(self):       return self.s ** 2
    def perimeter(self):  return 4 * self.s

for s in [Circle(5), Square(4)]:
    s.describe()

try:
    Shape()   # TypeError: Can't instantiate abstract class
except TypeError as e:
    print(e)

Abstract property

from abc import ABC, abstractmethod

class Animal(ABC):
    @property
    @abstractmethod
    def sound(self) -> str: ...

    def speak(self):
        print(f"{type(self).__name__} says: {self.sound}")

class Dog(Animal):
    @property
    def sound(self):
        return "Woof"

class Cat(Animal):
    @property
    def sound(self):
        return "Meow"

Dog().speak()   # Dog says: Woof
Cat().speak()   # Cat says: Meow

Virtual subclasses and __subclasshook__

from abc import ABC, abstractmethod

class Drawable(ABC):
    @abstractmethod
    def draw(self): ...

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Drawable:
            if any("draw" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

class Widget:
    def draw(self):
        print("drawing widget")

# Widget doesn't inherit from Drawable but has draw()
print(isinstance(Widget(), Drawable))   # True (via __subclasshook__)
print(issubclass(Widget, Drawable))     # True

Common Mistakes

  • Using `@abstractmethod` without inheriting from `ABC` or `ABCMeta` the decorator has no effect and the class can be instantiated with methods missing
  • Forgetting to call `super().__init__()` in concrete subclasses skips ABC's bookkeeping
  • Confusing `register()` with inheritance registered classes don't inherit any implementation from the ABC

Note

Use ABCs when you want to define a contract (interface) that multiple unrelated classes must satisfy. For internal class hierarchies, plain inheritance with `raise NotImplementedError` in base methods is often simpler and suffices.

Exercises

Write Python code to solve each exercise. Run it in your browser and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What happens when you try to instantiate a class with unimplemented @abstractmethod methods?