Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

Python Custom isinstance() and issubclass() Checks

Learn how Python customizes isinstance() and issubclass() through __instancecheck__() and __subclasscheck__() on a metaclass, including virtual relationships and preserving normal inheritance checks.

Levels: Advanced ~25 min read

Advanced

Python's isinstance() and issubclass() normally answer whether an object or class belongs to a type hierarchy. Python also lets a class customize those questions through methods on its metaclass. This lesson focuses on that advanced but useful Core Python data-model protocol.

Customize isinstance() with __instancecheck__()

When Python evaluates isinstance(value, SomeClass), it can call __instancecheck__() on the metaclass of SomeClass. The method receives the candidate object and returns the custom answer.

python
1
2
3
4
5
6
7
8
9
class NumberMeta(type):
    def __instancecheck__(cls, value):
        return isinstance(value, int) and value % 2 == 0

class EvenNumber(metaclass=NumberMeta):
    pass

print(isinstance(8, EvenNumber))
print(isinstance(7, EvenNumber))

EvenNumber is not a base class of int. The metaclass decides that even integers count as instances for this check.

Customize issubclass() with __subclasscheck__()

Similarly, issubclass(candidate, SomeClass) can be customized by __subclasscheck__() on the metaclass of SomeClass. The candidate is a class, and the hook decides whether it should be considered a subclass.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class RunnableMeta(type):
    def __subclasscheck__(cls, candidate):
        return hasattr(candidate, "run")

class Runnable(metaclass=RunnableMeta):
    pass

class Job:
    def run(self):
        pass

class Report:
    pass

print(issubclass(Job, Runnable))
print(issubclass(Report, Runnable))

Job does not inherit from Runnable. It is accepted because the custom subclass check recognizes its run() method.

The hooks belong on the metaclass

A crucial detail is where Python looks for these methods. __instancecheck__() and __subclasscheck__() are looked up on the metaclass of the class being checked. Defining one directly on the ordinary class does not create the same customization point.

python
1
2
3
4
5
6
class Marker:
    def __instancecheck__(self, value):
        return True

print(isinstance(Marker(), Marker))
print(isinstance(123, Marker))

Marker defines a method with the same name, but it is an ordinary instance method of Marker, not the metaclass hook used by isinstance().

Preserve normal inheritance checks when useful

A custom hook can extend the normal type behavior instead of replacing it. The metaclass can delegate to super() and then add its own rule. This is useful when real subclasses should keep behaving normally.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class TaggedMeta(type):
    def __instancecheck__(cls, value):
        return super().__instancecheck__(value) or getattr(value, "tag", None) == "special"

class Tagged(metaclass=TaggedMeta):
    pass

class Child(Tagged):
    pass

class External:
    tag = "special"

print(isinstance(Child(), Tagged))
print(isinstance(External(), Tagged))
print(isinstance(object(), Tagged))

The normal inheritance check remains true for Child, while an unrelated object with the right marker is also accepted.

Subclass checks can use the same idea

The same delegation pattern works for __subclasscheck__(). Keeping the built-in inheritance answer and adding a protocol rule can make a custom type boundary easier to express.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class WorkerMeta(type):
    def __subclasscheck__(cls, candidate):
        return super().__subclasscheck__(candidate) or hasattr(candidate, "work")

class Worker(metaclass=WorkerMeta):
    pass

class Employee(Worker):
    pass

class Contractor:
    def work(self):
        pass

print(issubclass(Employee, Worker))
print(issubclass(Contractor, Worker))

Employee passes through ordinary inheritance; Contractor is accepted by the additional protocol rule.

Understand virtual relationships

A custom check can describe a relationship without changing __bases__ or __mro__. The classes involved are still ordinary classes with their real inheritance structures; the custom result only changes how these built-in checks answer a question.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class SerializableMeta(type):
    def __instancecheck__(cls, value):
        return hasattr(value, "to_dict")

    def __subclasscheck__(cls, candidate):
        return hasattr(candidate, "to_dict")

class Serializable(metaclass=SerializableMeta):
    pass

class User:
    def to_dict(self):
        return {"name": "Ada"}

print(isinstance(User(), Serializable))
print(issubclass(User, Serializable))
print(User.__bases__)

The checks report a virtual relationship, but User still has its ordinary class bases. No inheritance was added.

Do not confuse these hooks with ordinary class methods

These hooks are not ordinary class methods such as those taught earlier. Python looks them up on the class's type—the metaclass—because the second argument to isinstance() and the first argument to issubclass() is itself a class.

python
1
2
3
4
5
6
7
8
9
class RuleMeta(type):
    def __instancecheck__(cls, value):
        return isinstance(value, dict)

class MappingLike(metaclass=RuleMeta):
    pass

print(isinstance({"ready": True}, MappingLike))
print(isinstance([], MappingLike))

MappingLike uses its metaclass to customize the meaning of isinstance(..., MappingLike).

Use these hooks sparingly

Customizing instance and subclass checks can model protocol-style relationships, but it also makes type questions less obvious. Keep the rule small, document the relationship, and prefer ordinary inheritance when the relationship is truly structural in the class hierarchy.

The standard library also uses these mechanisms. For example, ABCMeta supports virtual subclasses and subclass hooks. Those facilities are a separate Standard Library topic and are not expanded here.

Key takeaways

  • isinstance(obj, Class) can be customized through __instancecheck__() on Class's metaclass.
  • issubclass(Subclass, Class) can be customized through __subclasscheck__() on Class's metaclass.
  • The customization methods belong on the metaclass, not as ordinary methods on the checked class.
  • super().__instancecheck__() and super().__subclasscheck__() can preserve normal inheritance behavior while adding a rule.
  • Custom checks can describe virtual relationships without changing __bases__ or __mro__.
Was this page helpful?

Mark this learning resource complete to track your learning progress.