Python Abstract Base Classes in Action

Photo by Markus Spiske on Pexels.com

Abstract base classes

Python has this idea of abstract base classes (ABCs), which define a set of methods and properties that a class must implement in order to be considered a duck-type instance of that class. The class can extend the abstract base class itself in order to be used as an instance of that class, but it must supply all the appropriate methods.

Case study

Recently, I worked on an internal tool to parse source files containing test cases written in three different kinds of testing frameworks i.e. pytest, Robot Framework, and Cucumber. Since the tool needs to parse source files with three different parsers with a same set of APIs, it is advisable to create an abstract base class in this case to document what APIs the parsers should provide (documentation is one of the stronger use cases for ABCs). The abc module provides the tools I need to do this, as demonstrated in the following block of code:

from abc import ABC, abstractmethod
from pathlib import Path


class Parser(ABC):
    @property
    def name(self) -> str:
        return self._name

    @property
    def test_case_indicator(self) -> str:
        return self._test_case_indicator

    @abstractmethod
    def filter_test_case_from_source_file(self, path: Path) -> str:
        pass


class PytestParser(Parser):
    def __init__(self, source_file: Path) -> None:
        self._name = 'pytest'
        self._file_ext = 'py'
        self._test_case_indicator = 'def '

    def filter_test_case_from_source_file(self, path: Path) -> str:
        pass


class RobotParser(Parser):
    def __init__(self, source_file: Path) -> None:
        self._name = 'robot'
        self._file_ext = 'robot'
        self._test_case_indicator = '*** Test Cases ***'

    def filter_test_case_from_source_file(self, path: Path) -> str:
        pass


class GherkinParser(Parser):
    def __init__(self, source_file: Path) -> None:
        self._name = 'gherkin'
        self._file_ext = 'feature'
        self._test_case_indicator = 'Scenario: '

    def filter_test_case_from_source_file(self, path: Path) -> str:
        pass

Though I omitted the implementation of the filter_test_case_from_source_file() method for brevity, the idea is that any subclass of the Parser class must implement this method, as it is decorated with @abstractmethod.

More common object-oriented languages (like Java, Kotlin) have a clear separation between the interface and the implementation of a class. For example, some languages provide an explicit interface keyword that allows us to define the methods that a class must have without any implementation. In such an environment, an abstract class is one that provides both an interface and a concrete implementation of some, but not all, methods. Any class can explicitly state that it implements a given interface.
Python’s ABCs help to supply the functionality of interfaces without compromising on the benefits of duck typing.

References

PEP-3119

Leave a comment