Pythonでサブクラス・スーパークラスを確認(issubclass(), __mro__など)
Pythonで、クラスAがクラスBのサブクラス(子クラス、派生クラス)であるか、すなわち、クラスBがクラスAのスーパークラス(親クラス、基底クラス)であるかを判定するには、組み込み関数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'>)
__bases__
ではなく__base__
属性もあるが、多重継承しているクラスでは一つのスーパークラスしか取得できないので注意。
print(bool.__base__)
# <class 'int'>
print(Sub.__base__)
# <class '__main__.BaseA'>
クラスのサブクラスをすべて取得: __subclasses__()
あるクラスのサブクラス(子クラス、派生クラス)をすべて取得するには__subclasses__()
を使う。__bases__
とは異なり属性ではなくメソッドなので()
を忘れないように注意。
以下は例外クラスの例。見やすくするためにpprintを使っている。
import pprint
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によって諸々のモジュールがインポートされている状態での結果である。