メタクラス — いつ本当に必要か
クラスを作るクラスを扱います。type の正体、__init_subclass__ との分担、クラスデコレータで解けるケース、そして本当にメタクラスが必要な狭い領域までまとめます。
第16章 ディスクリプタと __set_name__ で属性をオブジェクト化する道具を見ましたが、本章は クラスそのものをオブジェクトとして扱う 道具 — メタクラスです。「上級 Python」の象徴のように頻繁に言及されますが、実際のコードでは使う場面をかなり絞る方が安全です。何がメタクラスなのか、そしてより軽い代替手段でどこまで解けるかを整理します。
本章の核心メッセージは「メタクラスが解いてくれる仕事の 90% は、第12章 デコレータパターン のクラスデコレータ、または第15章 マジックメソッド の __init_subclass__ で解ける」です。本当にメタクラスが必要な狭い領域を識別するのが本章の目的です。
出発点 — クラスもオブジェクト #
Python では クラスもオブジェクト です。
class User:
pass
print(type(User)) # <class 'type'>
print(User.__class__) # <class 'type'>User クラスの 型は type です。つまり User は type のインスタンスです。すべてのクラスはある型 (メタクラス) のインスタンスとして作られます。デフォルトのメタクラスが type です。
type でクラスを動的に作る
#
def greet(self):
print(f"hi, {self.name}")
# 等価: class User: ...
User = type("User", (object,), {
"name": "default",
"greet": greet,
})
u = User()
u.greet() # hi, defaulttype(name, bases, namespace) が クラスを動的に作る 関数です。実は class User: という構文が内部的にこれを呼び出します。
これは 滅多に使わない機能 ですが、メタクラスの正体を示しています — メタクラスとは結局 type のサブクラスです。
メタクラスの定義 #
class Meta(type):
def __new__(mcs, name, bases, namespace):
print(f"クラスを作成中: {name}")
return super().__new__(mcs, name, bases, namespace)
class User(metaclass=Meta):
pass
# 出力: クラスを作成中: Usermetaclass=Meta でどのメタクラスを使うかを指定します。クラスが定義されるその瞬間 に Meta.__new__ が呼ばれます。
__init_subclass__ — メタクラスが必要ないとき
#
第15章 で簡単に見た __init_subclass__ が ほぼすべてのメタクラスの仕事を解決します。 メタクラスなしで。
class Plugin:
registry = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.registry.append(cls)
class JsonPlugin(Plugin): pass
class CsvPlugin(Plugin): pass
print(Plugin.registry)
# [<class 'JsonPlugin'>, <class 'CsvPlugin'>]同じ仕事をメタクラスでやると。
class PluginMeta(type):
registry = []
def __init__(cls, name, bases, namespace):
super().__init__(name, bases, namespace)
if name != "Plugin":
PluginMeta.registry.append(cls)
class Plugin(metaclass=PluginMeta): pass
class JsonPlugin(Plugin): pass
class CsvPlugin(Plugin): pass長く、ベースクラス自体を登録しないための if name != "Plugin" のような例外処理も必要です。一般的な自動登録は __init_subclass__ の方が単純 です。
クラスデコレータ — もう一つの代替 #
第12章 デコレータパターン のクラスデコレータも、メタクラスがやる仕事の相当部分をこなします。
def register(cls):
Plugin.registry.append(cls)
return cls
class Plugin:
registry = []
@register
class JsonPlugin(Plugin): pass
@register
class CsvPlugin(Plugin): pass明示的だという長所があります。暗黙的な登録より明示的な登録 が良いことが多いです。
ではメタクラスはいつ? #
次のような場合にメタクラスが最も適しています。
1) type 自体の動作を変えたいとき
#
isinstance、issubclass のような検査の動作をメタクラスが決定します。ABC (abc.ABCMeta) がその例です。
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, data): ...
class FileStorage(Storage):
def save(self, data): ...
class BadStorage(Storage):
pass
s = FileStorage() # OK
b = BadStorage() # ✗ TypeError: Can't instantiate abstract class@abstractmethod のあるクラスをインスタンス化できないようにする動作が メタクラスレベル で起きます。このような場合は __init_subclass__ で解くのが難しいか、不可能です。
2) DSL — クラス定義自体を新しい構文のように #
class User(Model):
id = IntegerField(primary_key=True)
name = StringField(max_length=50)このようなコードでメタクラスがやる仕事。
- どのフィールドが定義されているかを収集
- テーブル名の決定
_metaのようなメタデータオブジェクトの生成- 検証メソッドの自動追加
Django ORM、SQLAlchemy、Peewee などがこのパターンを使います。クラス定義を一種の DSL として 使うときにメタクラスが必要です。第25章 DB 連携 — SQLAlchemy 2.x + Alembic の ORM モデルがこれに当たります。
3) 多重継承で一貫した動作を保証 #
複数のベースクラスが それぞれ異なるメタクラス を持つと衝突します。メタクラス一段上ですべての動作を統制したいとき。
これは一般のコードではほとんどありません。ライブラリ作成者に該当する領域です。
メタクラス vs 他の道具 — 決定ガイド #
| 仕事 | 最初の選択 |
|---|---|
| サブクラスを自動登録 | __init_subclass__ |
| 1 つのクラスに動作を追加 (eq、repr など) | クラスデコレータ (@dataclass など) |
| 属性アクセスをフック | ディスクリプタ |
| インスタンスメソッド / 属性を追加 | 通常のメソッド / 属性 |
isinstance / issubclass の動作変更 | メタクラス |
| ベースクラスの定義自体を DSL のように | メタクラス |
| クラス定義時に構文を検証 | メタクラスまたは __init_subclass__ |
判断基準: __init_subclass__ またはクラスデコレータで解けるなら、そちらがたいてい軽く済みます。メタクラスは それらで解けないとき に選びます。
__init_subclass__ の実力
#
本フックがどれほど多くの仕事をこなせるかを見れば、メタクラスが減る理由が見えてきます。
自動登録 #
class Handler:
handlers = {}
def __init_subclass__(cls, *, route=None, **kwargs):
super().__init_subclass__(**kwargs)
if route:
Handler.handlers[route] = cls
class HomeHandler(Handler, route="/"): pass
class AboutHandler(Handler, route="/about"): pass
print(Handler.handlers)
# {'/': <class 'HomeHandler'>, '/about': <class 'AboutHandler'>}サブクラスの定義部に route="/" のような 引数を渡せる ことも、よく知られていない事実です。__init_subclass__ がその引数を受け取ります。
必須メソッドの強制 #
class Service:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for method in ("start", "stop"):
if not hasattr(cls, method):
raise TypeError(f"{cls.__name__} は {method} が必要")
class Good(Service):
def start(self): ...
def stop(self): ...
class Bad(Service): # ✗ TypeError
def start(self): ...abc.ABC よりも軽い強制 — 仕事を直接定義できます。
クラス変数の検証 #
class Form:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if not hasattr(cls, "name"):
raise TypeError(f"{cls.__name__}.name が必要")
if not isinstance(cls.name, str):
raise TypeError("name は str")
class LoginForm(Form):
name = "login"__class_getitem__ — ジェネリック構文のフック
#
MyClass[int] のような表記を可能にするフック。
class Box:
def __class_getitem__(cls, item):
return f"Box[{item.__name__}]"
print(Box[int]) # Box[int]list[int]、dict[str, int] のようなビルトインジェネリックが本フックの上に作られています。ユーザークラスをジェネリックにするとき (Python 3.12 の class Foo[T]: 新構文以前の方式)、本フックが仕事をしていました。新しいコードは第9章 typing 本格 の class Stack[T]: 構文を使います。
type.__call__ — インスタンス生成のフロー
#
User() が実行されると。
type(User).__call__(User)が呼ばれる (メタクラスの__call__)- その中で
User.__new__(User)でインスタンス生成 User.__init__(instance)で初期化
本フローをフックするにはメタクラスの __call__ をオーバーライドします。シングルトン、インスタンスキャッシュのような場合に登場します。
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=SingletonMeta):
def __init__(self):
self.history = []
a = Logger()
b = Logger()
print(a is b) # True__new__ だけでもできる仕事ですが、__init__ が 2 回呼ばれてしまう罠 があり、メタクラスの方が安全な場合があります。
メタクラス衝突 #
サブクラスのメタクラスは すべての親のメタクラスのサブタイプでなければ なりません。
class MetaA(type): pass
class MetaB(type): pass
class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass
class C(A, B): pass
# TypeError: metaclass conflict複数のライブラリのクラスを多重継承するときに出会う頭痛の種 — だからライブラリ作成者もメタクラスを慎重に使います。
練習問題 #
__init_subclass__で自動登録器を作ってください。class Handler:のサブクラス定義部にroute="/path"のようなキーワード引数を受け取り、Handler.routes: dict[str, type]に登録します。HomeHandler、AboutHandlerの 2 つのサブクラスで動作を確認します。__init_subclass__で必須メソッドの強制検証を作ってください。サブクラスがstart/stopメソッドを持たない場合、定義時点でTypeErrorが発生します。- メタクラス
SingletonMetaでシングルトンパターンを実装してください。同じクラスの 2 回目の呼び出しが最初のインスタンスを返すか確認します。同じ仕事を__new__だけで実装したあと、両方式の罠 (特に__init__が毎回呼ばれるか) を比較します。
一行まとめ: クラスもオブジェクト、クラスの型が
type(デフォルトメタクラス)。メタクラスはtypeのサブクラス。一般的なメタクラスの仕事の 90% 以上は__init_subclass__またはクラスデコレータで解ける。本当にメタクラスが必要なのは ABC、ORM DSL、多重継承の一貫性のような狭い領域。メタクラス衝突が頻繁なのでなるべく使わない方が良い。
次の章 #
次の 第18章 非同期の深さ — イベントループ、gather/wait、async generator では、第14章 非同期入門 で見た asyncio の次の段階 — イベントループの動作、gather / wait の微妙な違い、async generator、並行性パターンを扱います。