Check Subclass and Superclass in Python: issubclass, __bases__

Modified: | Tags: Python

In Python, you can use the built-in issubclass() function to check if a class is a subclass (child class) of another.

Additionally, you can get all superclasses of a class using the __mro__ attribute, direct superclasses using the __bases__ attribute, and all subclasses using the __subclasses__() method.

For methods to get and check the type of an object, see the following article.

Check if a class is a subclass: issubclass()

Use the built-in issubclass() function to check if a class is a subclass (child class) of another.

issubclass() returns True if the class in the first argument is a subclass of the class in the second. Note that a class is considered a subclass of itself.

For example, you can confirm that the boolean type bool is a subclass of the integer type int as follows.

print(issubclass(bool, int))
# True

print(issubclass(bool, float))
# False

print(issubclass(bool, bool))
# True

The second argument can be a tuple of classes. It returns True if the first class is a subclass of any of the classes in the tuple.

print(issubclass(bool, (int, float)))
# True

print(issubclass(bool, (str, float)))
# False

issubclass() also returns True for higher-level parent classes, not just direct superclasses.

For example, consider the inheritance chain of exception types: BaseException -> Exception -> ArithmeticError -> ZeroDivisionError.

print(issubclass(ZeroDivisionError, ArithmeticError))
# True

print(issubclass(ZeroDivisionError, Exception))
# True

print(issubclass(ZeroDivisionError, BaseException))
# True

The first argument of issubclass() must be a class. Specifying an instance (object) will result in an error.

# print(issubclass(True, int))
# TypeError: issubclass() arg 1 must be a class

print(issubclass(type(True), int))
# True

type() can be used to get the class of an object, but isinstance() is more appropriate for checking if an object is an instance of a specific class.

Like issubclass(), isinstance() allows you to specify a higher-level parent class or a tuple of multiple classes as the second argument.

print(isinstance(True, bool))
# True

print(isinstance(True, int))
# True

print(isinstance(True, float))
# False

print(isinstance(True, (int, float)))
# True

For more details, see the following article.

Get all superclasses of a class: __mro__

Use the __mro__ attribute to get all superclasses (parent classes) of a class. MRO stands for Method Resolution Order.

The object class is the superclass of all classes.

print(bool.__mro__)
# (<class 'bool'>, <class 'int'>, <class 'object'>)

print(type(True).__mro__)
# (<class 'bool'>, <class 'int'>, <class 'object'>)
source: mro_bases.py

__mro__ returns a tuple of type objects.

print(type(bool.__mro__))
# <class 'tuple'>

print(type(bool.__mro__[0]))
# <class 'type'>
source: mro_bases.py

To get class names, you can access the __name__ attribute of type. The following example uses list comprehension.

print([c.__name__ for c in bool.__mro__])
# ['bool', 'int', 'object']
source: mro_bases.py

Here is how it looks for exception classes. The pprint module is used to enhance readability.

import pprint

pprint.pprint(ZeroDivisionError.__mro__)
# (<class 'ZeroDivisionError'>,
#  <class 'ArithmeticError'>,
#  <class 'Exception'>,
#  <class 'BaseException'>,
#  <class 'object'>)
source: mro_bases.py

Generally, it follows the superclass order starting from the class itself, but not all adjacent classes necessarily have a direct parent-child relationship. For example, in the case of multiple inheritance as shown below, BaseA and BaseB are sequentially listed in __mro__ without being directly related.

class BaseA(): pass
class BaseB(): pass
class Sub(BaseA, BaseB): pass

pprint.pprint(Sub.__mro__)
# (<class '__main__.Sub'>,
#  <class '__main__.BaseA'>,
#  <class '__main__.BaseB'>,
#  <class 'object'>)
source: mro_bases.py

Get direct superclasses of a class: __bases__

Use the __bases__ attribute to get only the direct superclasses (parent classes). Unlike __mro__, it does not trace back to a higher-level parent.

print(bool.__bases__)
# (<class 'int'>,)
source: mro_bases.py

__bases__ returns a tuple of type objects. For a single direct superclass, the tuple has one element.

print(type(bool.__bases__))
# <class 'tuple'>

print(type(bool.__bases__[0]))
# <class 'type'>
source: mro_bases.py

For object, it is an empty tuple.

print(object.__bases__)
# ()
source: mro_bases.py

For classes with multiple inheritance, it is a tuple with multiple elements.

class BaseA(): pass
class BaseB(): pass
class Sub(BaseA, BaseB): pass

print(Sub.__bases__)
# (<class '__main__.BaseA'>, <class '__main__.BaseB'>)
source: mro_bases.py

Note that there is also the __base__ attribute, but it can only obtain one superclass in classes with multiple inheritance.

print(bool.__base__)
# <class 'int'>

print(Sub.__base__)
# <class '__main__.BaseA'>
source: mro_bases.py

Get all subclasses of a class: __subclasses__()

Use the __subclasses__() method to get all subclasses (child classes) of a class.

Below is an example with exception classes. The pprint module is used to make it more readable.

import pprint

pprint.pprint(ArithmeticError.__subclasses__())
# [<class 'FloatingPointError'>,
#  <class 'OverflowError'>,
#  <class 'ZeroDivisionError'>,
#  <class 'decimal.DecimalException'>]

__subclasses__() returns a list of type objects.

print(type(ArithmeticError.__subclasses__()))
# <class 'list'>

print(type(ArithmeticError.__subclasses__()[0]))
# <class 'type'>

It includes only the direct subclasses.

class Base(): pass
class SubA(Base): pass
class SubB(Base): pass
class SubSubA(SubA): pass
class SUbSubB(SubB): pass

print(Base.__subclasses__())
# [<class '__main__.SubA'>, <class '__main__.SubB'>]

Some examples of recursively retrieving all subclasses can be found in the following Stack Overflow thread.

An example using a set is as follows.

# https://stackoverflow.com/a/3862957
def all_subclasses(cls):
    return set(cls.__subclasses__()).union(
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])

pprint.pprint(all_subclasses(Base))
# {<class '__main__.SubA'>,
#  <class '__main__.SubB'>,
#  <class '__main__.SubSubA'>,
#  <class '__main__.SUbSubB'>}

Note that the results of __subclasses__() are updated with module imports and other changes.

print(len(dict.__subclasses__()))
# 24

import pandas as pd

print(len(dict.__subclasses__()))
# 29

The example above is executed in Jupyter Notebook, where various modules have been imported by IPython.

Related Categories

Related Articles