Mathew K Analytics

Lesson 24 · Python standard library deep dive

Python abc & inspect Explained: Abstract Classes & Introspection | Standard Library #24

Video twenty-four of the twenty-five-part series: abc, for abstract base classes, and inspect, for runtime introspection. Abstract methods, abstract…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

Python Standard Library Deep-Dive, Video 24: abc and inspect#

  • Video twenty-four of the twenty-five-part series: abc, for abstract base classes, and inspect, for runtime introspection.
  • Abstract methods, abstract properties, virtual subclasses, signatures, source inspection, and the call stack.
  • Let's get into it.

Part 1: Why Abstract Base Classes, and ABC Basics#

from abc import ABC, abstractmethod
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
try:
    Shape()
except TypeError as e:
    print(f'caught: {e}')
caught: Can't instantiate abstract class Shape without an implementation for abstract method 'area'

Part 2: @abstractmethod - Enforcing Subclass Implementation#

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return 3.14159 * self.radius ** 2
c = Circle(3)
print(c.area())
class Incomplete(Shape):
    pass
try:
    Incomplete()
except TypeError as e:
    print(f'caught: {e}')
28.27431
caught: Can't instantiate abstract class Incomplete without an implementation for abstract method 'area'

Part 3: ABC with Concrete Helper Methods (Template Method Pattern)#

class Shape2(ABC):
    @abstractmethod
    def area(self):
        pass
    def describe(self):
        return f'This shape has an area of {self.area():.2f}'
class Square(Shape2):
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side ** 2
sq = Square(4)
print(sq.describe())
This shape has an area of 16.00

Part 4: Abstract Properties#

class Vehicle(ABC):
    @property
    @abstractmethod
    def wheels(self):
        pass
class Car(Vehicle):
    @property
    def wheels(self):
        return 4
car = Car()
print(car.wheels)
try:
    car.wheels = 6
except AttributeError as e:
    print(f'caught: {e}')
4
caught: property 'wheels' of 'Car' object has no setter

Part 5: isinstance/issubclass and Virtual Subclasses with register()#

class Flyer(ABC):
    @abstractmethod
    def fly(self):
        pass
class Drone:
    def fly(self):
        return 'buzzing off'
print(isinstance(Drone(), Flyer))
Flyer.register(Drone)
print(isinstance(Drone(), Flyer))
print(issubclass(Drone, Flyer))
False
True
True

Part 6: inspect.signature() - Examining Function Signatures#

import inspect
def greet(name: str, greeting: str = 'Hello', *, shout: bool = False) -> str:
    result = f'{greeting}, {name}!'
    return result.upper() if shout else result
sig = inspect.signature(greet)
print(sig)
for param_name, param in sig.parameters.items():
    print(param_name, param.default, param.annotation)
(name: str, greeting: str = 'Hello', *, shout: bool = False) -> str
name <class 'inspect._empty'> <class 'str'>
greeting Hello <class 'str'>
shout False <class 'bool'>

Part 7: inspect.getmembers() and getsource()#

class Toolbox:
    def hammer(self):
        pass
    def wrench(self):
        pass
    value = 42
methods = inspect.getmembers(Toolbox, predicate=inspect.isfunction)
print([name for name, _ in methods])
import textwrap
print(inspect.getsource(textwrap.shorten))
['hammer', 'wrench']
def shorten(text, width, **kwargs):
    """Collapse and truncate the given text to fit in the given width.

    The text first has its whitespace collapsed.  If it then fits in
    the *width*, it is returned as is.  Otherwise, as many words
    as possible are joined and then the placeholder is appended::

        >>> textwrap.shorten("Hello  world!", width=12)
        'Hello world!'
        >>> textwrap.shorten("Hello  world!", width=11)
        'Hello [...]'
    """
    w = TextWrapper(width=width, max_lines=1, **kwargs)
    return w.fill(' '.join(text.strip().split()))

Part 8: inspect.isfunction(), isclass(), ismethod()#

def plain_function():
    pass
class SampleClass:
    def bound_method(self):
        pass
instance = SampleClass()
print(inspect.isfunction(plain_function))
print(inspect.isclass(SampleClass))
print(inspect.ismethod(instance.bound_method))
print(inspect.ismethod(plain_function))
print(inspect.ismodule(inspect))
True
True
True
False
True

Part 9: inspect.stack() and currentframe() - Call Stack Introspection#

def inner_function():
    frame = inspect.currentframe()
    print(frame.f_code.co_name)
    caller_frame = frame.f_back
    print(caller_frame.f_code.co_name)
def outer_function():
    inner_function()
outer_function()
def log_caller():
    stack = inspect.stack()
    print(f'called from: {stack[1].function}')
def some_caller():
    log_caller()
some_caller()
inner_function
outer_function
called from: some_caller

Part 10: A Real Pattern - a Plugin System with ABC and inspect#

class Plugin(ABC):
    @abstractmethod
    def run(self, data):
        pass
class UppercasePlugin(Plugin):
    def run(self, data):
        return data.upper()
class ReversePlugin(Plugin):
    def run(self, data):
        return data[::-1]
def discover_plugins(module_namespace):
    plugins = []
    for name, obj in module_namespace.items():
        if inspect.isclass(obj) and issubclass(obj, Plugin) and obj is not Plugin:
            plugins.append(obj)
    return plugins
found = discover_plugins(globals())
print(sorted(p.__name__ for p in found))
for plugin_cls in found:
    instance = plugin_cls()
    print(f'{plugin_cls.__name__}: {instance.run("hello")}')
['ReversePlugin', 'UppercasePlugin']
UppercasePlugin: HELLO
ReversePlugin: olleh

Wrap-Up: What You Learned#

  • ABC plus @abstractmethod defines a required interface; Python refuses to instantiate a subclass missing any of it.
  • A concrete subclass must override every abstract method to become instantiable at all.
  • ABCs can mix abstract and concrete methods; a concrete method can call an abstract one, the template method pattern.
  • Stacking @property under @abstractmethod requires subclasses to provide an attribute-like value, not just a method.
  • register() creates virtual subclasses for isinstance/issubclass checks, with no real inheritance required at all.
  • inspect.signature() reveals a callable's parameters, defaults, and annotations without calling it.
  • inspect.getmembers()/getsource() list attributes and retrieve real original source code text.
  • inspect.isfunction/isclass/ismethod/ismodule are runtime type predicates beyond plain isinstance.
  • inspect.stack()/currentframe() introspect the real call stack, discovering callers dynamically.
  • A real pattern: an ABC-defined plugin interface, discovered automatically at runtime with inspect.
  • That wraps up abc and inspect. Next up: the capstone, a real CLI tool combining everything from this series.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.