PythonのClassの生成に関してまとめてみた
TL;DR
Pythonのクラス、色々ありすぎて難しいのでまとめます。まずはClassの生成についてです。
Classの生成
- まず、
__new__メソッドが呼ばれ、その後__init__メソッドが呼ばれる。 __new__メソッド内では、クラス変数の定義などが行われる。__init__メソッド内では、インスタンス変数が定義できる。- クラス変数は、クラスから作成されたインスタンス全てが利用できる変数で、インスタンス変数はそれぞれのインスタンスからのみ利用できる変数。
- クラス変数はインスタンスが生成されていなくても使えて、クラス変数を利用するメソッドには
@classmethodのデコレータを付けて明示する。 - 生成されたインスタンスは
self、クラスそのものはclsと慣用的に命名される。
__new__と__init__の挙動確認
のようにクラスを定義します。@staticmethodはクラス変数にも生成されたインスタンス(cls, selfを引数に取らない)メソッドのデコレータで、このメソッドもインスタンスが生成されてなくても使えます。
以下の様に、@classmethodや@staticmethodで定義されている部分については、インスタンスを生成しなくても使えていることがわかります。
次にインスタンスの生成過程を見てみます。
インスタンスが生成された後のIDを見ると、__new__で生成されているIDは同じでクラス自身が生成されています。また、__init__で生成されているIDは異なっていて、違うインスタンスが生成されていることがわかります。
さらに、クラス変数を変更した場合の挙動を確認します。
どちらかの側でクラス変数を変更すると、両方のインスタンスでクラス変数が変更されていることがわかります。
__new__は何に使うのか
__new__の使用例としては、
-
そのクラスが何回呼び出されたのかを記録する
-
tupleなどのimmutableなオブジェクトの初期化
-
メタクラスを利用してクラスを切り替える
などが挙げられます。公式的には、2,3が想定用途っぽいです (参考: 3. データモデル)。
__new__()の主な目的は、変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズすることにあります。また、クラス生成をカスタマイズするために、カスタムのメタクラスでよくオーバーライドされます。
動的なクラス定義
typeがobjectの引数1つを取った場合、object.__class__を返します。これによって、以下のようにクラスを判別することができます。
クラスの生成のためには、name, bases, dictの3つの引数を取ります。これらの値は__name__, __bases__, __dict__に対応しています。__bases__は継承で、() or (object,)が最も基本的なクラスです。通常のクラス生成との対応は以下です。
つまり、通常のクラス生成は__new__内でtypeのインスタンス化が行われていた、ということです。ここで重要なのは、typeはclassです(参考: 組み込み関数)。この辺りの関係性はPythonのオブジェクトとクラスのビジュアルガイド – 全てがオブジェクトであるということなどが参考になります。
なので、typeを継承したクラスを使って__new__内でそのクラスを呼び出せば、クラスの生成を動的にカスタマイズできます。この概念がメタクラスです。
メタクラスの実例
メタクラスは「クラスのクラス」です。通常のクラスがインスタンスの振る舞いを定義するように、メタクラスはクラス自体の生成過程を制御します。typeを継承して__new__をオーバーライドすることで、独自のメタクラスを定義できます。
基本的なメタクラス — 属性の自動追加
最もシンプルな例として、クラス生成時に属性を自動で追加するメタクラスを定義してみます。
metaclass=AutoAttrMetaと指定することで、class文の実行時にtype.__new__の代わりにAutoAttrMeta.__new__が呼ばれます。引数のmcsはメタクラス自身(AutoAttrMeta)、nameはクラス名、basesは基底クラスのタプル、namespaceはクラス本体で定義された属性の辞書です。
Registry パターン — クラスの自動登録
実務で最もよく使われるメタクラスのパターンの一つが、クラスの自動登録(Registry)です。サブクラスを定義するだけで自動的に辞書に登録され、プラグインシステムやシリアライゼーションの仕組みに活用できます。
このパターンでは、新しいサブクラスを定義するだけで自動的に登録されるため、登録漏れが起きません。プラグインシステムやコマンドディスパッチなど、拡張性が求められる設計で威力を発揮します。
Singleton パターン — インスタンス生成の制御
メタクラスの__call__をオーバーライドすると、インスタンスの生成過程を制御できます。__new__がクラスの生成を制御するのに対し、__call__はそのクラスが呼び出された時(= インスタンス生成時)の挙動を制御します。
ここで重要なのは、メタクラスにおける__new__と__call__の役割の違いです。
__new__:class文が実行された時に呼ばれる。クラスオブジェクト自体を生成する__call__: 生成済みのクラスがMyClass()のように呼び出された時に呼ばれる。インスタンスの生成を制御する
この区別は、記事の冒頭で説明した通常のクラスにおける__new__(インスタンスの生成)と__init__(インスタンスの初期化)の関係と似ています。メタクラスでは一段階上のレベルで同様の制御が行われているわけです。
まとめ
メタクラスは非常に強力な機能ですが、日常的なPythonプログラミングで必要になることは多くありません。Python 3.6以降では、__init_subclass__を使うことで、Registryパターンのようなユースケースをメタクラスなしで実現できます。
メタクラスを使う前に、まずデコレータや__init_subclass__で目的が達成できないか検討するのが良いでしょう。それでも足りない場合に、メタクラスの出番です。