Abstract Classes & Interfaces

Difficulty: Intermediate

Question

How does Python handle abstract classes and interfaces? What are Protocols?

Answer

Python provides abstract classes through the abc module. Abstract classes cannot be instantiated and can define abstract methods that subclasses must implement.

Python also embraces duck typing: 'If it walks like a duck and quacks like a duck, it's a duck.' You don't need explicit interfaces.

Protocols (Python 3.8+, typing module) provide structural subtyping - a class satisfies a Protocol if it has the required methods, without explicit inheritance. This bridges the gap between duck typing and static type checking.

Code examples

Abstract Base Classes (ABC)

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Calculate the area of the shape."""
        pass
    
    @abstractmethod
    def perimeter(self):
        """Calculate the perimeter of the shape."""
        pass
    
    def describe(self):
        """Non-abstract method: shared by all shapes."""
        return f"{self.__class__.__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}"

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)

# shape = Shape()  # TypeError: Can't instantiate abstract class
c = Circle(5)
r = Rectangle(3, 4)
print(c.describe())  # Circle: area=78.54, perimeter=31.42
print(r.describe())  # Rectangle: area=12.00, perimeter=14.00

Abstract classes enforce a contract: subclasses MUST implement all abstract methods. Non-abstract methods provide shared default behavior.

Protocols: Structural Subtyping

from typing import Protocol, runtime_checkable

@runtime_checkable
class Drawable(Protocol):
    def draw(self) -> str:
        ...

class Circle:
    def draw(self) -> str:
        return "Drawing circle"

class Square:
    def draw(self) -> str:
        return "Drawing square"

class Text:
    def render(self) -> str:  # Different method name
        return "Rendering text"

# No explicit inheritance needed!
def render_all(items: list[Drawable]) -> None:
    for item in items:
        print(item.draw())

shapes = [Circle(), Square()]
render_all(shapes)  # Works!

# Runtime checking with @runtime_checkable
print(isinstance(Circle(), Drawable))  # True
print(isinstance(Text(), Drawable))    # False - no draw() method

Protocols define interfaces structurally: any class with matching methods satisfies the protocol, without inheriting from it. This is Python's answer to Go's interfaces.

Duck Typing in Practice

# Duck typing: focus on behavior, not type
def get_length(obj):
    """Works with anything that has __len__."""
    return len(obj)

print(get_length([1, 2, 3]))     # 3 - list
print(get_length("hello"))       # 5 - string
print(get_length({1, 2, 3}))     # 3 - set
print(get_length({'a': 1}))      # 1 - dict

# EAFP: Easier to Ask Forgiveness than Permission
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None
    except TypeError:
        return None

# vs LBYL (Look Before You Leap) - less Pythonic
def safe_divide_lbyl(a, b):
    if isinstance(b, (int, float)) and b != 0:
        return a / b
    return None

# ABC registration: make existing classes satisfy an ABC
from abc import ABC, abstractmethod

class Printable(ABC):
    @abstractmethod
    def __str__(self):
        pass

# All objects have __str__, so register built-in types
Printable.register(int)
Printable.register(str)
print(isinstance(42, Printable))      # True
print(isinstance("hello", Printable)) # True

Python's EAFP style: try the operation and handle failure, rather than checking types first. ABC.register() retroactively makes classes satisfy an ABC.

Key points

Concepts covered

ABC, Abstract Methods, Interfaces, Protocols, Duck Typing