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.
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.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.