9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

"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__ インスタンス作成時に呼ばれる

いつ使う?

  1. ほぼ使わない(__init_subclass__ で十分)
  2. フレームワーク開発時
  3. ORMなどの「魔法」を作りたい時
9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?