0Pricing
Python Academy · レッスン

カスタムメタクラスの作成

メタクラスで __new__ と __init__ を使い、クラス作成をカスタマイズします。

「カスタムメタクラスの作成」はCoddyKit上の無料Python Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPython Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Python Academyコースには全4レッスンが含まれています。

メタクラスの宣言

クラス定義でmetaclass=MyMetaを指定すると、カスタムメタクラスを使用できます。メタクラスはtypeを継承する必要があります。

class MyMeta(type):
    pass

class Foo(metaclass=MyMeta):
    pass

print(type(Foo))   # <class '__main__.MyMeta'>

__new__のオーバーライド

__new__(mcs, name, bases, namespace)はクラスオブジェクトを作成します。クラスが構築される前に名前空間を変更するために使用します。

class UpperAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        upper = {k.upper(): v for k, v in namespace.items()
                 if not k.startswith("_")}
        return super().__new__(mcs, name, bases, {**namespace, **upper})

class Config(metaclass=UpperAttrMeta):
    debug = True

print(Config.DEBUG)  # True

__init__のオーバーライド

__init__(cls, name, bases, namespace)は、クラスの作成後に実行されます。登録など、作成後のセットアップに使用します。

_registry = {}

class AutoRegisterMeta(type):
    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        if bases:   # skip the base class itself
            _registry[name] = cls

class Handler(metaclass=AutoRegisterMeta): pass
class JSONHandler(Handler): pass
class XMLHandler(Handler):  pass

print(_registry)  # {'JSONHandler': ..., 'XMLHandler': ...}

インターフェース契約の強制

クラス本体に必須メソッドがない場合は、__new__でTypeErrorを発生させます。

class InterfaceMeta(type):
    REQUIRED = frozenset({"process", "validate"})

    def __new__(mcs, name, bases, ns):
        if bases and not mcs.REQUIRED.issubset(ns):
            missing = mcs.REQUIRED - ns.keys()
            raise TypeError(f"{name} must define: {missing}")
        return super().__new__(mcs, name, bases, ns)

class Worker(metaclass=InterfaceMeta):
    def process(self): ...
    def validate(self): ...

メタクラスによるシングルトン

メタクラスの__call__をオーバーライドすると、インスタンスの作成を横取りし、毎回同じインスタンスを返せます。

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *a, **kw):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*a, **kw)
        return cls._instances[cls]

class DB(metaclass=SingletonMeta):
    pass

print(DB() is DB())  # True

メソッドの動的な追加

メタクラスを使用するすべてのクラスに、各クラスを個別に変更することなくユーティリティメソッドを注入できます。

class MethodInjectorMeta(type):
    def __new__(mcs, name, bases, ns):
        ns["describe"] = lambda self: f"I am {type(self).__name__}"
        return super().__new__(mcs, name, bases, ns)

class Widget(metaclass=MethodInjectorMeta):
    pass

print(Widget().describe())   # I am Widget

順序付き名前空間のための__prepare__

__prepare__をオーバーライドして、クラスの名前空間としてカスタムマッピングを提供します。たとえば、定義順序を保持したり重複を検出したりできます。

class NoDuplicatesMeta(type):
    class _NS(dict):
        def __setitem__(self, k, v):
            if k in self and not k.startswith("_"):
                raise ValueError(f"Duplicate attribute: {k}")
            super().__setitem__(k, v)

    @classmethod
    def __prepare__(mcs, name, bases):
        return mcs._NS()

メタクラスの競合解決

複数の基底クラスが異なるメタクラスを持つ場合、Pythonでは最も派生したメタクラスを明示的に使用する必要があります。そうしないと競合エラーが発生します。

class MetaA(type): pass
class MetaB(type): pass

# class Bad(A, B): pass  # TypeError: metaclass conflict

class MetaAB(MetaA, MetaB): pass   # resolve manually

class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass
class Good(A, B, metaclass=MetaAB): pass

メタクラスと__init_subclass__

サブクラスの登録や単純なフックには、メタクラスが不要な__init_subclass__のほうが簡単です。

class Base:
    _registry = []
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        Base._registry.append(cls)

class ChildA(Base): pass
class ChildB(Base): pass

print(Base._registry)  # [ChildA, ChildB]

属性アクセスのトレース

メタクラスの__getattribute__をオーバーライドすると、インスタンスではなくクラスに対する属性アクセスを横取りできます。

class TraceMeta(type):
    def __getattribute__(cls, name):
        val = super().__getattribute__(name)
        if not name.startswith("_"):
            print(f"Accessing class attr: {name}")
        return val

class Config(metaclass=TraceMeta):
    HOST = "localhost"

_ = Config.HOST   # logs: Accessing class attr: HOST

ORMフィールド収集パターン

Django形式のORMは、メタクラスを使用してクラス本体からFieldディスクリプターを収集し、_metaレジストリに格納します。

class Field:
    def __init__(self, col): self.col = col

class ModelMeta(type):
    def __new__(mcs, name, bases, ns):
        fields = {k: v for k, v in ns.items() if isinstance(v, Field)}
        ns["_fields"] = fields
        return super().__new__(mcs, name, bases, ns)

class User(metaclass=ModelMeta):
    name = Field("name")
    email = Field("email")

print(User._fields.keys())  # dict_keys(['name', 'email'])

確認問題

カスタムメタクラスで、クラスオブジェクト自体を作成するために(__init__より前に)呼び出されるメソッドはどれですか。

まとめ

カスタムメタクラスは__new__/__init__をオーバーライドしてクラス作成を制御し、契約の強制、サブクラスの自動登録、メソッドの注入、フィールドディスクリプターの収集などを行います。単純な登録が必要な場合は__init_subclass__を優先してください。

よくある質問

「カスタムメタクラスの作成」レッスンは無料ですか?

はい。「カスタムメタクラスの作成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Python Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Python Academyコースには全4レッスンが含まれています。

「カスタムメタクラスの作成」で何を学びますか?

メタクラスで __new__ と __init__ を使い、クラス作成をカスタマイズします。 ブラウザで直接実行するハンズオンコードでPython Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Python Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPython Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「カスタムメタクラスの作成」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPython Academyレッスンでコードを書いて実行できますか?

はい。すべてのPython Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Python のクラスが作成される仕組み
  2. カスタムメタクラスの作成
  3. ディスクリプター: __get__、__set__、__delete__
  4. __slots__ とメモリ最適化
← Python Academyに戻る