27.5 isinstance() and issubclass()
The isinstance() and issubclass() functions are foundational tools in Python’s type system, providing a robust mechanism for checking object and class relationships. They are essential for implementing polymorphism, validating function arguments, and writing flexible code that can handle a variety of types. Understanding their behavior, especially in the context of inheritance and multiple inheritance, is critical for any advanced Python developer.
The isinstance() Function
The isinstance(object, classinfo) function returns True if the object argument is an instance of the classinfo argument, or an instance of a (direct, indirect, or virtual) subclass thereof. If classinfo is a tuple of type objects, the function will return True if object is an instance of any of the types in the tuple.
class Vehicle:
pass
class Car(Vehicle):
pass
class Boat(Vehicle):
pass
my_car = Car()
# Basic usage
print(isinstance(my_car, Car)) # True
print(isinstance(my_car, Vehicle)) # True (due to inheritance)
print(isinstance(my_car, Boat)) # False
# Using a tuple of types for multiple checks
print(isinstance(my_car, (Boat, Vehicle, str))) # True (because it's a Vehicle)
A key strength of isinstance() is its compatibility with abstract base classes (ABCs) from the collections.abc or typing modules. It can check for a “virtual subclass” that has registered with the ABC, even if there is no explicit inheritance. This allows for structural type checking based on implemented methods rather than a strict class hierarchy.
from collections.abc import Iterable
class MyCustomSequence:
def __iter__(self):
return iter([]) # Just return an empty iterator
# MyCustomSequence is not a subclass of Iterable
print(issubclass(MyCustomSequence, Iterable)) # False
# But it has an __iter__ method, so it is considered an instance
custom_seq = MyCustomSequence()
print(isinstance(custom_seq, Iterable)) # True
The issubclass() Function
The issubclass(class, classinfo) function returns True if class is a (direct, indirect, or virtual) subclass of classinfo. As with isinstance(), classinfo can be a tuple of class objects.
print(issubclass(Car, Vehicle)) # True
print(issubclass(Car, Car)) # True (a class is considered a subclass of itself)
print(issubclass(Car, (Boat, Vehicle))) # True
# Checking with ABCs
print(issubclass(MyCustomSequence, Iterable)) # False (unless explicitly registered)
Interaction with Multiple Inheritance and the MRO
The behavior of both isinstance() and issubclass() is governed by the Method Resolution Order (MRO) of a class. The MRO is the order in which base classes are searched for a method or attribute. When checking for a subclass or instance relationship, Python consults the MRO list of the class being checked.
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C): # Multiple inheritance
pass
# Check the MRO
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
# isinstance and issubclass use this order
d_instance = D()
print(isinstance(d_instance, A)) # True - A is in D's MRO
print(issubclass(D, C)) # True - C is in D's MRO
This MRO-based lookup is why these functions work seamlessly with complex multiple inheritance hierarchies. They aren’t fooled by the diamond problem; they simply follow the linearized order defined by the C3 algorithm.
Common Pitfalls and Best Practices
Checking for Multiple Types: Always use a tuple for checking against multiple types. This is more efficient and readable than multiple
orconditions.# Good if isinstance(obj, (int, float)): ... # Less efficient and more verbose if isinstance(obj, int) or isinstance(obj, float): ...Overusing
type(): A common mistake is usingtype(obj) is SomeClassinstead ofisinstance(obj, SomeClass). Thetype()check is strict and will fail for subclasses, breaking polymorphism.number = 5.0 # a float # This will not trigger for a float, which is a subclass of real numbers if type(number) is float: print("It's a float") # This is the correct, polymorphic approach if isinstance(number, float): print("It's a float or a subclass thereof")Abstract Base Classes (ABCs): For checking behavioral contracts (e.g., “is this object iterable?”), prefer using ABCs like
Iterable,Sequence, orMappingwithisinstance()over concrete classes. This makes your code far more flexible and adheres to the “duck typing” philosophy.Performance: While the overhead is usually negligible,
isinstance()can be slightly slower than atype()check because it must traverse the inheritance hierarchy. This should only be a concern in extremely performance-critical loops.Virtual Subclasses: Remember that a class registered with an ABC (e.g., using
ABCMeta.register()) will returnTrueforisinstance()checks butFalseforissubclass()checks unless it is also a real subclass. To makeissubclass()returnTrue, the class must be explicitly derived from the ABC or use the@registerdecorator.