Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Python

Custom isinstance and issubclass With Python Magic Methods

Learn how Python's __subclasscheck__ and __instancecheck__ magic methods let you control isinstance() and issubclass() behavior for flexible, protocol-friendly type checking without forcing inheritance.

July 2026 6 min read 1 views 0 hearts

The Secret Behind Python's isinstance() and issubclass() That Most Developers Miss

You've used isinstance() and issubclass() a thousand times. But have you ever wondered what makes them tick behind the scenes? Python has two magic methods — __subclasscheck__ and __instancecheck__ — that give you god-like control over these checks. And most developers don't even know they exist.

Let me show you how they work and why they matter.

The Default Behavior

Normally, when you write isinstance(obj, MyClass), Python looks at the class hierarchy. It checks if obj is an instance of MyClass or any of its subclasses. Same for issubclass().

But what if you want to override this logic? What if you want isinstance() to return True for objects that don't actually inherit from your class?

That's where __instancecheck__ and __subclasscheck__ come in.

The Magic Methods Demystified

These methods live on the metaclass level. You can't just drop them on a regular class. Here's the rule:

  • __instancecheck__(self, instance) is called when you do isinstance(instance, MyClass)
  • __subclasscheck__(self, subclass) is called when you do issubclass(subclass, MyClass)

And they must be defined on the metaclass, not the class itself.

A Real-World Example

Let's say you're building a plugin system at PythonSkillset. You want any object with a run() method to be considered a "Plugin", even if it doesn't inherit from your base class.

class PluginMeta(type):
    def __instancecheck__(self, instance):
        if hasattr(instance, 'run') and callable(instance.run):
            return True
        return super().__instancecheck__(instance)

    def __subclasscheck__(self, subclass):
        if hasattr(subclass, 'run'):
            return True
        return super().__subclasscheck__(subclass)

class PluginBase(metaclass=PluginMeta):
    pass

class MyPlugin:
    def run(self):
        print("Running!")

class NotAPlugin:
    pass

# Now this works!
print(isinstance(MyPlugin(), PluginBase))  # True
print(issubclass(MyPlugin, PluginBase))    # True
print(isinstance(NotAPlugin(), PluginBase))  # False

See what happened there? MyPlugin never inherits from PluginBase, but both isinstance() and issubclass() return True because the object has a run() method.

The ABC Connection

If this looks familiar, it should. Python's abc.ABC (Abstract Base Classes) uses exactly this mechanism. When you do isinstance(obj, collections.abc.Sequence), it's not checking inheritance — it's checking if the object implements the right methods.

from collections.abc import Sequence

class MyList:
    def __getitem__(self, index):
        return index
    def __len__(self):
        return 1

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

That's __instancecheck__ at work, courtesy of ABCMeta.

The Performance Trap

Here's something most articles won't tell you: overusing __instancecheck__ can kill your performance. Every time you call isinstance(), Python runs your custom logic. If that logic is expensive (like iterating over all attributes), you'll feel the pain in tight loops.

Use it sparingly. Only when you truly need structural typing over nominal typing.

The Protocol Pattern

At PythonSkillset, we use this pattern for "protocol-like" behavior. Instead of forcing users to inherit from a base class, we let them satisfy an interface implicitly:

class RunnableCheckMeta(type):
    def __instancecheck__(self, instance):
        return hasattr(instance, 'run')

class Runnable(metaclass=RunnableCheckMeta):
    """Can check if something is runnable without forcing inheritance."""
    pass

class Worker:
    def run(self):
        pass

def execute(task):
    if isinstance(task, Runnable):
        task.run()
    else:
        raise TypeError("Not runnable")

This gives you flexible, duck-typing-friendly code without the rigidity of traditional inheritance.

When NOT to Use Them

  • For simple type checks: Just use normal inheritance. Don't overcomplicate things.
  • In performance-critical paths: The metaclass hook adds overhead.
  • When you need clear class hierarchies: Custom checks can confuse readers. Your team might not expect isinstance() to lie.

The Bottom Line

__subclasscheck__ and __instancecheck__ are Python's best-kept secrets for writing flexible, protocol-friendly code. They let you separate "what something is" from "what something inherits from." But with great power comes great responsibility — use them wisely, and your code will be cleaner. Abuse them, and you'll have a debugging nightmare.

Next time you need to check if something "acts like" a certain type, remember: you have the power to redefine what "is" means in Python.

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.