note.nkmk.me

Pythonでサブクラス・スーパークラスを確認(issubclass(), __mro__など)

Posted: 2021-02-07 / Tags: Python

Pythonで、クラスAがクラスBのサブクラス(派生クラス、子クラス)であるか、すなわち、クラスBがクラスAのスーパークラス(基底クラス、親クラス)であるかを判定するには組み込み関数issubclass()を使う。

また、あるクラスのスーパークラスの一覧をすべて取得するには__mro__属性、直接のスーパークラスのみを取得するには__bases__属性、サブクラスの一覧をすべて取得するには__subclasses__()を使う。

ここでは以下の内容について説明する。

  • 指定したクラスのサブクラスか判定: 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'>)
source: mro_bases.py

type型オブジェクトを要素とするタプルが格納されている。

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

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

type__name__属性で名前を取得したりできる。

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

例外クラスの例。見やすくするためにpprintを使っている。

import pprint

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

基本的には上の例のように自分自身のクラスからスーパークラスを遡った順番となるが、必ずしも連続するクラスに親子関係があるとは限らない。例えば以下のように複数のクラスを継承(多重継承)している場合、BaseABaseBが順に__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'>)
source: mro_bases.py

クラスの直接のスーパークラスを取得: bases

直接のスーパークラスのみを取得するには__bases__属性を使う。__mro__のように親の親まで遡らない。

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

type型オブジェクトを要素とするタプルが格納されている。直接のスーパークラスがひとつだけの場合は要素数1個のタプル。

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

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

objectの場合は空のタプルとなる。

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

多重継承しているクラスの場合は複数要素のタプルとなる。

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

__base__属性も存在するが、多重継承しているクラスでは一つのスーパークラスしか取得できないので注意。

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

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

クラスのサブクラスをすべて取得: 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によって諸々のモジュールがインポートされている状態での結果である。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事