概要
タイトルがわかりづらくて恐縮なのですが、以下の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
参考
- Effective Python 第2版 ―Pythonプログラムを改良する90項目 | Brett Slatkin, 石本 敦夫, 黒川 利明 |本 | 通販 | Amazon
- How to auto register a class when it's defined
- 【Python】特殊メソッド一覧 - Qiita