2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Pythonで親クラスを継承した子クラスの数とインスタンス数を数える

Last updated at Posted at 2022-02-18

概要

タイトルがわかりづらくて恐縮なのですが、以下の2つの数え方についてこの記事では紹介します。

  • 親クラスを継承した子クラスの数を数えたい
  • 親クラスに属する(継承した子クラスの)インスタンスの数を数えたい

もう少し別のバリエーションだと、

  • 親クラスを継承したすべての子クラスのクラスメソッドを呼びたい
  • 親クラスに属するすべてのインスタンスに対して指示を出したい

ということも実現可能です。

サンプルコードはGitHubに公開しています。

検証環境

  • Python 3.10.0

親クラスを継承した子クラスの数を数えたい

親クラスがを継承する子クラスが2つあるとして、その2という数字を求めます。

class Parent():
    _subclasses_count = 0

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        __class__._subclasses_count += 1

    @classmethod
    @property
    def subclasses_count(cls):
        return __class__._subclasses_count

class ChildA(Parent):
    pass

class ChildB(Parent):
    pass

def main():
    print(Parent.subclasses_count)  # 2

if __name__ == '__main__':
    main()

ポイントは__init_subclass__です。Python 3.6から使えます。これは、クラスが継承されるたびに毎回呼ばれる特殊メソッドで、この中でクラス変数の_subclasses_countをインクリメントしていきます。__init_subclass__のおかげで、継承先の子クラスではなにもしなくても子クラスの数を求めることが可能になりました。

__init__subclass__は子クラスのさらに子クラスにも効きます。

class ChildA(Parent):
    pass

class ChildB(Parent):
    pass

class ChildAChildC(ChildA):
    pass

class ChildAChildD(ChildA):
    pass

このような場合は4が返ってきます。

本題と関係ないですが、@classmethod@propertyの重ねがけはPython 3.9以降でないと使えない1ため、Python 3.8以前ではメソッドから参照するか、直接参照する必要があります。

    # Python 3.8以前の場合
    @classmethod
    def get_subclasses_count_v3_8(cls):
        return __class__._subclasses_count

親クラスに属するインスタンスの数を数えたい

親クラスを継承した子クラスのインスタンスが合計3つあるとして、その3という数字を求めます。

class Parent():
    _instances_count = 0

    def __new__(cls):
        self = super().__new__(cls)
        __class__._instances_count += 1
        return self

    @classmethod
    @property
    def instances_count(cls):
        return __class__._instances_count

class ChildA(Parent):
    pass

class ChildB(Parent):
    pass

def main():
    child_a_1 = ChildA()
    child_a_2 = ChildA()
    child_b = ChildB()

    print(Parent.instances_count)  # 3

if __name__ == '__main__':
    main()

こちらのポイントは__new__です。インスタンスの作成方法を定義できる特殊メソッドで、__init__と似ていますが、それより前に呼ばれます。普通はインスタンスを作成してreturnするもののようですが、この中で_instances_countをインクリメントする処理をはさめます。

前節と同様に、子クラスの子クラス(つまり孫クラス)のインスタンスも数えられます。

親クラスを継承したすべての子クラスのクラスメソッドを呼びたい

各子クラスに定義しているクラスメソッドnotifyを呼びます。
親クラスを継承した子クラスの数を数えたいと仕組みは同じなのでソースコードだけ貼ります。

from abc import ABC, abstractclassmethod

class Parent(ABC):
    all_subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        __class__.all_subclasses.append(cls)

    @classmethod
    def notify_all_subclasses(cls):
        for subclass in __class__.all_subclasses:
            subclass.notify()

    @abstractclassmethod
    def notify(cls):
        pass

class ChildA(Parent):
    @classmethod
    def notify(cls):
        print(f'{__class__.__name__} subclass')

class ChildB(Parent):
    @classmethod
    def notify(cls):
        print(f'{__class__.__name__} subclass')

def main():
    Parent.notify_all_subclasses()

if __name__ == '__main__':
    main()

実行結果

❯ python subclass_notifier.py
ChildA subclass
ChildB subclass

親クラスに属するすべてのインスタンスに対して指示を出したい

各子クラスに定義しているインスタンスメソッドnotifyを呼びます。
こちらも親クラスに属するインスタンスの数を数えたいと仕組みは同じなのでソースコードだけ貼ります。

from abc import ABC, abstractmethod

class Parent(ABC):
    all_instances = []

    def __new__(cls):
        self = super().__new__(cls)
        __class__.all_instances.append(self)
        return self

    @classmethod
    def notify_all_instances(cls):
        for instance in __class__.all_instances:
            instance.notify()

    @abstractmethod
    def notify(self):
        pass

class ChildA(Parent):
    def notify(self):
        print(f'{__class__.__name__} instance')

class ChildB(Parent):
    def notify(self):
        print(f'{__class__.__name__} instance')

def main():
    child_a_1 = ChildA()
    child_a_2 = ChildA()
    child_b = ChildB()

    Parent.notify_all_instances()

if __name__ == '__main__':
    main()

実行結果。

❯ python instance_notifier.py
ChildA instance
ChildA instance
ChildB instance

参考

  1. https://docs.python.org/ja/3.9/library/functions.html#classmethod

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?