Python 3: Deep Dive (Part 4 - OOP): メタクラス (セクション14-2/15)
Pythonのクラスはすべて`type`のインスタンスであり、クラス定義時に`type`が呼び出される
`type`をサブクラス化(メタクラス)することで、クラス生成の仕組みを自由に拡張できる
メタクラスは強力だがコードが複雑化しやすいので、通常はデコレータなどで代替し、やむを得ない場面でのみ使う
Pythonのクラスシステムは、見た目以上に多くの仕組みが存在します。一般的には `class MyClass: ...` を「オブジェクトのための設計図を定義する」と考えがちですが、実はその裏側にはより高度な仕組みがあります。Pythonのクラス自体が特別なクラス `type` のインスタンスであることを理解することで、クラスを作る「クラス」であるメタクラスの力を活用できるようになります。
以下では、クラスが作成されるメカニズム、`type` が内部でどのように機能するか、`type` をオーバーライドする(つまりサブクラス化する)方法、そして Python の `metaclass` 引数が、手作業で多くのコードを書かずにクラス生成をより直感的に行わせるしくみを解説します。
クラスはすべて `type` のオブジェクト
まず大前提として:Python で定義するすべてのクラスは、`type` のインスタンスです。 例えば:
class Person:
pass
このとき、Python は内部的に以下のような手順を踏んでいます。
クラス名(`Person`)やベースクラス(ここでは既定で `object`)、クラス本体(メソッドやプロパティ、クラス変数などの定義)を収集する。
クラス用の名前空間辞書を作成する。ここにクラスの「属性」(メソッドや変数など)が入る。
クラス本体のコードをその名前空間辞書内で実行し、辞書を埋める。
`type(class_name, class_bases, class_dict)` を呼び出して**新しい型(typeインスタンス)**を作る。これがクラスオブジェクトであり、Python はこのインスタンスをモジュールの名前空間で `Person` というシンボルに束縛する。
概念的にはこう書けます:
Person = type(
"Person", # クラス名
(object,), # ベースクラス
{ ...class body... } # メソッドや変数が入った名前空間
)
`type` はクラス(そしてクラスは呼び出し可能)なので、これを呼ぶと新しい型オブジェクト、つまりクラスオブジェクトが返ってきます。
クラス作成を手動でエミュレートする
Pythonのステップを自前で再現してみることもできます。たとえばクラス `Circle` を作りたいとしましょう。
import math
class_body = """
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def area(self):
return math.pi * self.r**2
"""
# 1. クラス用の名前空間辞書を作る
class_dict = {}
# 2. クラス本体のコードをその辞書内で実行
exec(class_body, globals(), class_dict)
# 3. type(...) を使ってクラスオブジェクトを作成
Circle = type('Circle', (), class_dict)
# 4. これで普通のクラスとして使える
c = Circle(0, 0, 1)
print(c.area()) # 3.141592653589793
`exec(...)` を呼ぶと、Python はその文字列をコンパイルして `class_dict` を使って実行するため、`init` と `area` が `class_dict` に格納されます。そこから `type("Circle", (), class_dict)` を呼び出すと新しい型インスタンス(クラス)を得られ、それが `Circle` として利用できるようになります。これは、ソースコードで `class Circle: ...` と書いたときと同じクラスですが、より詳しく制御する手順を辿ったわけです。
`type` のサブクラス化:独自の型を作る
`type` がクラスであるとわかったので、これをサブクラス化してクラス作成を傍受・拡張できます。クラス定義のたびに何らかのメソッドを自動追加したり、クラスの属性を加工したりするロジックを仕込みたいときなどに使います。
以下はカスタムな「メタ」typeの基本構造です。
import math
class CustomType(type):
def __new__(mcls, name, bases, class_dict):
print("Customized type creation!")
# 1. 通常の type.__new__ を使ってクラスを作る
cls_obj = super().__new__(mcls, name, bases, class_dict)
# 2. 必要に応じて作成したクラスを追加で操作
cls_obj.circumference = lambda self: 2 * math.pi * self.r
return cls_obj
こうして `CustomType("Circle", (), {...})` と呼び出せば、`type("Circle", (), {...})` ではなく私たちの `new` が動きます。結果として、クラスの型は `CustomType` になります(もっとも `CustomType` は `type` のサブクラスなので、依然として普通にクラスとして振る舞います)。このプロセスの途中で `.circumference()` をクラスに注入するなど、様々な拡張が可能になります。
メタクラス:宣言的に使う近道
文字列コードや名前空間辞書を明示的に書くのは面倒ですが、Python ではクラス定義の中で `metaclass` を指定することで、この手順を自動化してくれます。
class Circle(metaclass=CustomType):
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def area(self):
return math.pi * self.r**2
Pythonがこのクラス定義を読み込むとき、クラス名 (`"Circle"`)・本体・ベースクラスなどをまとめるのは従来どおりですが、最終的に `type` ではなく `CustomType` を呼び出してクラスを作ります。つまり、`CustomType.new` には以下の引数が渡されます。
`mcls` → `CustomType`(メタクラスそのもの)
`name` → `"Circle"`(クラス名)
`bases` → `()`(ベースクラスのタプル)
`class_dict` → クラス本体から集めた属性・メソッドなど
ここで、私たちは `super().new` を呼ぶ前後で、自由にクラスを操作できます。クラス定義時に特殊ロジックを挟むための非常に強力なフックです。
メタクラスとは何か
メタクラスは「クラスのクラス」。デフォルトでは `type`。
クラスの `metaclass` を指定するだけで、そのクラス生成の仕組みを自由に差し替えられる。
メタクラスの `new` がクラス完成前に呼び出されるので、その中でクラスを検証したり、属性を改変したりできる。
Python は常に `class MyClass: ...` を処理するときにこの「メタクラス」を使いますが、通常は `type` を使い、必要に応じて拡張可能にしているわけです。
なぜメタクラスは(通常)あまり使われないのか
メタクラスはその強大な力と同時に、しばしばコードの可読性を損ねます。どのようなクラス振る舞いがメタクラスによって付与されるのかが分かりにくくなり、保守が難しくなるからです。Tim Peters の有名な言葉があります。
日常的なアプリケーションコードでは、クラスデコレータなどでも十分に実現できるケースが多く、メタクラスほど複雑にする必要はあまりありません。逆に、ライブラリやフレームワークなど、クラスの生成段階で高度なカスタマイズが必要な場面ではメタクラスが役立ちます。それ以外のケースでは大袈裟すぎるかもしれません。
まとめ
クラスはすべて `type` のインスタンス
Python のクラス定義は、最終的に `type` を呼び出すことでクラスオブジェクトを作ります。クラス作成は再現可能
`exec()` と `type()` を使い、Python がクラスを組み立てる手順を自力で再現できます。`type` のサブクラスでカスタマイズ
`type` を継承し、`new` をオーバーライドすれば、クラス作成自体を拡張できます。これは一種のメタプログラミングです。メタクラス
`class X(metaclass=...)` と書くだけで、Python が裏で行うクラス生成に割り込めます。文字列コードを手打ちする必要はありません。使いどころを見極める
メタクラスは強力ですが、コードを複雑にしがちです。ライブラリやフレームワークの設計など、どうしてもクラス生成レベルで仕組みをいじりたい場合に限って利用すると良いでしょう。
こうして Python クラスが本当にどのように作られているかを知ると、デスクリプタやデコレータ、オブジェクトモデル全般への理解がより深まります。日常的にメタクラスを書くことはなくても、その裏側を理解することで、Python の柔軟性をより一層活用できるでしょう。そしてもし本当にメタクラスを使うのであれば、「大きなハンマー」を使うのに見合うだけの問題を解決しようとしているかを、常に考えることが大切です。