Pythonでサブクラス・スーパークラスを確認(issubclass(), __mro__など)
Pythonで、クラスAがクラスBのサブクラス(派生クラス、子クラス)であるか、すなわち、クラスBがクラスAのスーパークラス(基底クラス、親クラス)であるかを判定するには組み込み関数issubclass()
を使う。
また、あるクラスのスーパークラスの一覧をすべて取得するには__mro__
属性、直接のスーパークラスのみを取得するには__bases__
属性、サブクラスの一覧をすべて取得するには__subclasses__()
を使う。
- 組み込み型 -
__mro__
— Python 3.9.1 ドキュメント - 組み込み型 -
__bases__
— Python 3.9.1 ドキュメント - 組み込み型 -
__subclasses__()
— Python 3.9.1 ドキュメント
ここでは以下の内容について説明する。
- 指定したクラスのサブクラスか判定:
issubclass()
- クラスのスーパークラスをすべて取得:
__mro__
- クラスの直接のスーパークラスを取得:
__bases__
- クラスのサブクラスをすべて取得:
__subclasses__()
オブジェクトの型(クラス)を取得・判定する方法については以下の記事を参照。
指定したクラスのサブクラスか判定: issubclass()
あるクラスがあるクラスのサブクラス(派生クラス、子クラス)かを判定するには組み込み関数issubclass()
を使う。
第一引数に指定したクラスが第二引数に指定したクラスのサブクラスである(= 第二引数のクラスが第一引数のクラスのスーパークラスである)とTrue
が返される。クラスはそれ自身のサブクラスとみなされるので第一引数と第二引数が同じクラスの場合もTrue
。
例えばブール型bool
は整数型int
のサブクラスなので、以下の結果となる。
print(issubclass(bool, int))
# True
print(issubclass(bool, float))
# False
print(issubclass(bool, bool))
# True
第二引数はクラスを要素とするタプルでもよい。いずれかのクラスのサブクラスであればTrue
となる。
print(issubclass(bool, (int, float)))
# True
print(issubclass(bool, (str, float)))
# False
第二引数が直接のスーパークラスでなく、さらに親のクラスでもTrue
となる。
例えば、例外型においてBaseException
-> Exception
-> ArithmeticError
-> ZeroDivisionError
と派生しているので、以下の結果となる。
print(issubclass(ZeroDivisionError, ArithmeticError))
# True
print(issubclass(ZeroDivisionError, Exception))
# True
print(issubclass(ZeroDivisionError, BaseException))
# True
issubclass()
の第一引数にはクラスを指定する。実際のインスタンス(オブジェクト)を指定するとエラー。
# print(issubclass(True, int))
# TypeError: issubclass() arg 1 must be a class
print(issubclass(type(True), int))
# True
上の例のようにtype()
を使ってもよいが、オブジェクトがあるクラスのインスタンスであるかの判定には組み込み関数isinstance()
が使える。
issubclass()
と同様に、isinstance()
でも第二引数に親の親のクラスを指定して判定したり、タプルで複数のクラスを指定したりできる。
print(isinstance(True, bool))
# True
print(isinstance(True, int))
# True
print(isinstance(True, float))
# False
print(isinstance(True, (int, float)))
# True
詳細は以下の記事を参照。
クラスのスーパークラスをすべて取得: mro
あるクラスのスーパークラス(基底クラス、親クラス)をすべて取得するには__mro__
属性を使う。MROはMethod Resolution Order(メソッド順序解決)の意。
object
はすべてのクラスのスーパークラス。
print(bool.__mro__)
# (<class 'bool'>, <class 'int'>, <class 'object'>)
print(type(True).__mro__)
# (<class 'bool'>, <class 'int'>, <class 'object'>)
type
型オブジェクトを要素とするタプルが格納されている。
print(type(bool.__mro__))
# <class 'tuple'>
print(type(bool.__mro__[0]))
# <class 'type'>
type
は__name__
属性で名前を取得したりできる。
- 関連記事: Pythonリスト内包表記の使い方
print([c.__name__ for c in bool.__mro__])
# ['bool', 'int', 'object']
例外クラスの例。見やすくするためにpprintを使っている。
import pprint
pprint.pprint(ZeroDivisionError.__mro__)
# (<class 'ZeroDivisionError'>,
# <class 'ArithmeticError'>,
# <class 'Exception'>,
# <class 'BaseException'>,
# <class 'object'>)
基本的には上の例のように自分自身のクラスからスーパークラスを遡った順番となるが、必ずしも連続するクラスに親子関係があるとは限らない。例えば以下のように複数のクラスを継承(多重継承)している場合、BaseA
とBaseB
が順に__mro__
に格納されるが、2つのクラスに親子関係はない。
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'>)
クラスの直接のスーパークラスを取得: bases
直接のスーパークラスのみを取得するには__bases__
属性を使う。__mro__
のように親の親まで遡らない。
print(bool.__bases__)
# (<class 'int'>,)
type
型オブジェクトを要素とするタプルが格納されている。直接のスーパークラスがひとつだけの場合は要素数1個のタプル。
print(type(bool.__bases__))
# <class 'tuple'>
print(type(bool.__bases__[0]))
# <class 'type'>
object
の場合は空のタプルとなる。
print(object.__bases__)
# ()
多重継承しているクラスの場合は複数要素のタプルとなる。
class BaseA(): pass
class BaseB(): pass
class Sub(BaseA, BaseB): pass
print(Sub.__bases__)
# (<class '__main__.BaseA'>, <class '__main__.BaseB'>)
__base__
属性も存在するが、多重継承しているクラスでは一つのスーパークラスしか取得できないので注意。
print(bool.__base__)
# <class 'int'>
print(Sub.__base__)
# <class '__main__.BaseA'>
クラスのサブクラスをすべて取得: subclasses()
あるクラスのサブクラスをすべて取得するには__subclasses__()
を使う。__bases__
とは異なり属性ではなくメソッドなので()
を忘れないように注意。
pprint.pprint(ArithmeticError.__subclasses__())
# [<class 'FloatingPointError'>,
# <class 'OverflowError'>,
# <class 'ZeroDivisionError'>,
# <class 'decimal.DecimalException'>]
type
型オブジェクトを要素とするリストが返される。
print(type(ArithmeticError.__subclasses__()))
# <class 'list'>
print(type(ArithmeticError.__subclasses__()[0]))
# <class 'type'>
__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'>]
再帰的にサブクラスのサブクラスまですべて取得する例は以下のStack Overflowの回答でいくつか示されている。
集合set
を使った例は以下の通り。
# 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'>}
なお、__subclasses__()
の結果はモジュールのインポートなどで更新される。
print(len(dict.__subclasses__()))
# 24
import pandas as pd
print(len(dict.__subclasses__()))
# 29
上の例はJupyter Notebookで実行しており、IPythonによって諸々のモジュールがインポートされている状態での結果である。