はじめに
"If you wonder whether you need metaclasses, you don't."
「メタクラスが必要かどうか迷うなら、必要ない」 — Tim Peters
それでも知りたいあなたへ。
メタクラスとは?
メタクラス = クラスのクラス
インスタンス → クラス → メタクラス
↑ ↑ ↑
obj = Cls() class Cls: type
すべてのクラスは type のインスタンス
class Dog:
pass
print(type(Dog)) # => <class 'type'>
print(type(type)) # => <class 'type'> (自己参照!)
type() でクラスを動的に作れる
# 普通の定義
class Dog:
def bark(self):
return "Woof!"
# type() で同じこと
Dog = type('Dog', (), {'bark': lambda self: "Woof!"})
# type(クラス名, 親クラスのタプル, 属性の辞書)
メタクラスを自作する
class MyMeta(type):
def __new__(mcs, name, bases, namespace):
print(f"Creating class: {name}")
return super().__new__(mcs, name, bases, namespace)
class MyClass(metaclass=MyMeta):
pass
# => Creating class: MyClass
引数の意味
| 引数 | 説明 |
|---|---|
mcs |
メタクラス自身(慣習的にmcs) |
name |
作成するクラス名 |
bases |
親クラスのタプル |
namespace |
クラス本体の名前空間(辞書) |
実用例
シングルトン
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 Database(metaclass=SingletonMeta):
def __init__(self):
self.connection = "Connected"
db1 = Database()
db2 = Database()
print(db1 is db2) # => True
プラグイン自動登録
class RegistryMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases: # 基底クラス以外
mcs.registry[name] = cls
return cls
class Plugin(metaclass=RegistryMeta):
pass
class AudioPlugin(Plugin):
pass
class VideoPlugin(Plugin):
pass
print(RegistryMeta.registry)
# => {'AudioPlugin': <class 'AudioPlugin'>, 'VideoPlugin': ...}
必須メソッドの強制
class ValidatedMeta(type):
required_methods = ['handle', 'validate']
def __new__(mcs, name, bases, namespace):
if bases:
for method in mcs.required_methods:
if method not in namespace:
raise TypeError(f"{name} must implement {method}()")
return super().__new__(mcs, name, bases, namespace)
class Handler(metaclass=ValidatedMeta):
pass
class BadHandler(Handler): # TypeError!
pass
class GoodHandler(Handler):
def handle(self): pass
def validate(self): pass
__init_subclass__ - メタクラスの代替(Python 3.6+)
多くのケースでメタクラスは不要。__init_subclass__ で十分:
class PluginBase:
plugins = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.plugins.append(cls)
class PluginA(PluginBase):
pass
class PluginB(PluginBase):
pass
print(PluginBase.plugins)
# => [<class 'PluginA'>, <class 'PluginB'>]
比較
| 機能 | メタクラス | __init_subclass__ |
|---|---|---|
| クラス作成の制御 | ✅ | ❌ |
| サブクラス登録 | ✅ | ✅ |
| 属性の検証 | ✅ | ✅ |
| 複雑さ | 高 | 低 |
| 推奨度 | 最後の手段 | まずこれを検討 |
メタクラスの実際の使用例
Django ORM
class User(models.Model):
name = models.CharField(max_length=100)
# ↑ これがなぜ動く?メタクラスのおかげ
SQLAlchemy
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
Enum
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# Enumの魔法もメタクラス
メタクラスのライフサイクル
class MyMeta(type):
def __new__(mcs, name, bases, namespace):
# クラスオブジェクトを作成
print("1. __new__")
return super().__new__(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace):
# クラスオブジェクトを初期化
print("2. __init__")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
# クラスからインスタンスを作る時
print("3. __call__")
return super().__call__(*args, **kwargs)
class MyClass(metaclass=MyMeta):
pass
# => 1. __new__
# => 2. __init__
obj = MyClass()
# => 3. __call__
⚠️ 注意点
メタクラスの競合
class Meta1(type): pass
class Meta2(type): pass
class A(metaclass=Meta1): pass
class B(metaclass=Meta2): pass
class C(A, B): # TypeError!
pass
# メタクラスが競合
解決策:継承関係を整理する
class Meta2(Meta1): pass # Meta2がMeta1を継承
デバッグが困難
メタクラスはクラス定義時に動くため、エラーが出ると原因特定が難しい。
まとめ
| 概念 | 説明 |
|---|---|
| メタクラス | クラスを作るクラス |
type |
デフォルトのメタクラス |
__new__ |
クラスオブジェクト作成 |
__init__ |
クラスオブジェクト初期化 |
__call__ |
インスタンス作成時に呼ばれる |
いつ使う?
- ほぼ使わない(
__init_subclass__で十分) - フレームワーク開発時
- ORMなどの「魔法」を作りたい時