Python

How Python's __subclasshook__ Makes Classes Pretend to Be Related

Learn how Python's __subclasshook__ method lets abstract base classes recognize types by behavior rather than inheritance, with practical examples and gotchas to avoid.

August 2026 6 min read 13 views 0 hearts

Making Python Classes Pretend to Be Related: The Magic of __subclasshook__

Ever wished you could make a class that acts like it inherits from another, even when it technically doesn't? That's exactly what __subclasshook__ lets you do in Python. It's one of those tools that sounds like magic until you understand the mechanics behind it.

Let me show you how this works with a real example from Python itself.

The ABC Pattern You Already Know

You've probably used isinstance() checks with abstract base classes before. Python's built-in ABCs use __subclasshook__ internally. Consider this:

from collections.abc import Iterable

class MyList:
    def __iter__(self):
        return iter([1, 2, 3])

print(isinstance(MyList(), Iterable))  # True

Wait, how does Python know MyList is iterable? It doesn't inherit from Iterable. The secret is __subclasshook__ on the Iterable ABC.

How __subclasshook__ Works

The mechanism is simpler than you'd think. When you check isinstance(obj, SomeABC), Python doesn't just check inheritance. It also calls SomeABC.__subclasshook__(type(obj)). If that returns True, Python treats the object as a member of that ABC.

Let me demonstrate with a custom validator:

from abc import ABC, abstractmethod

class FileHandler(ABC):
    @abstractmethod
    def read(self):
        pass

    @abstractmethod
    def write(self, data):
        pass

    @classmethod
    def __subclasshook__(cls, subclass):
        # Check if the class has the required methods
        if cls is FileHandler:
            if (hasattr(subclass, 'read') and 
                hasattr(subclass, 'write')):
                return True
        return NotImplemented

Now look at this. Any class with read() and write() methods is automatically considered a FileHandler:

class CsvProcessor:
    def read(self):
        print("Reading CSV")

    def write(self, data):
        print("Writing CSV")

    # No mention of FileHandler whatsoever

print(isinstance(CsvProcessor(), FileHandler))  # True
print(issubclass(CsvProcessor, FileHandler))   # True

When NOT to Use This

Here's where most tutorials get it wrong. They make you think __subclasshook__ is for everything. It's not.

Use it when: - You're building a plugin system where third-party classes should be recognized without your library's imports - You're creating a protocol-like ABC for duck typing (Python 3.8+ actually has typing.Protocol for this purpose) - You need backward compatibility without forcing inheritance changes

Don't use it when: - Regular inheritance would work fine - You're just trying to be clever (trust me, your future self will hate you) - The behavior depends on runtime state (subclass checks should be deterministic)

A Practical Example from Pythonskillset

At Pythonskillset, we had this exact problem with our logging system. Different modules implemented logging differently. Instead of forcing everyone to inherit from a base class, we used __subclasshook__:

class PythonskillsetLogger(ABC):
    @classmethod
    def __subclasshook__(cls, subclass):
        if cls is PythonskillsetLogger:
            methods = ['log_info', 'log_error', 'log_warning']
            if all(hasattr(subclass, m) for m in methods):
                return True
        return NotImplemented

    @abstractmethod
    def log_info(self, msg: str): pass
    @abstractmethod
    def log_error(self, msg: str): pass
    @abstractmethod
    def log_warning(self, msg: str): pass

The result? Any class with those three methods works seamlessly with our system, no imports needed.

The Gotcha You Need to Know

Here's something that'll bite you: __subclasshook__ only works for classes, not instances. And it's checked at isinstance() time, not at class creation. This means:

class DynamicValidator:
    def __init__(self, has_methods):
        if has_methods:
            self.validate = lambda x: True

# This won't work because __subclasshook__ checks the class, not instance

Making It Bulletproof

Always include the if cls is MyClass guard. Without it, subclasses of your ABC might inherit unexpected behavior:

class SafeABC(ABC):
    @classmethod
    def __subclasshook__(cls, subclass):
        if cls is SafeABC:  # This guard is crucial
            # Your logic here
            return True
        return NotImplemented

Return NotImplemented (not False) when you can't decide. This lets Python's normal MRO and other ABCs in the chain have their say.

The Bottom Line

__subclasshook__ is Python's way of saying "actions speak louder than inheritance". It's elegant when used appropriately, but it's not a replacement for actual inheritance hierarchies. Think of it as a polite suggestion system: "If it walks like a duck and quacks like a duck, maybe it is a duck for our purposes."

Next time you're building a flexible system where classes should be recognized by their behavior rather than their parentage, remember this tool. Just don't forget the guard clause.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.