メタクラスを使うと、クラス定義時に関数やメソッドを自動的に登録する仕組みを作れます。プラグインシステムやコマンドパターンの実装に有用です。
基本的なアイデア
クラスが定義されるとき、メタクラスの __new__ または __init__ が呼ばれます。ここでクラスの属性を走査し、特定の条件を満たす関数を登録できます。
class PluginMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != "PluginBase":
mcs.registry[name] = cls
return cls
class PluginBase(metaclass=PluginMeta):
pass
class EmailPlugin(PluginBase):
def execute(self):
print("Sending email")
class SMSPlugin(PluginBase):
def execute(self):
print("Sending SMS")
print(PluginMeta.registry)
# {'EmailPlugin': <class 'EmailPlugin'>, 'SMSPlugin': <class 'SMSPlugin'>}
特定のデコレータが付いたメソッドを登録
メソッドにマーカーを付けて、それを自動収集するパターンです。
def command(name):
def decorator(func):
func._command_name = name
return func
return decorator
class CommandMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
cls._commands = {}
for attr_name, attr_value in namespace.items():
if hasattr(attr_value, "_command_name"):
cls._commands[attr_value._command_name] = attr_value
return cls
class Bot(metaclass=CommandMeta):
@command("hello")
def say_hello(self):
return "Hello!"
@command("bye")
def say_goodbye(self):
return "Goodbye!"
print(Bot._commands)
# {'hello': <function Bot.say_hello>, 'bye': <function Bot.say_goodbye>}
init_subclass による代替
Python 3.6 以降では、メタクラスを使わずに __init_subclass__ で同様のことができます。
class PluginBase:
registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
PluginBase.registry[cls.__name__] = cls
メタクラスはより強力ですが、__init_subclass__ で十分な場合はそちらを選ぶとコードがシンプルになります。