{
    "slug": "py_metaclass_patterns",
    "term": "Metaclass Patterns",
    "category": "python",
    "difficulty": "advanced",
    "short": "Metaclasses customize class creation itself — useful for registries, abstract base enforcement, and ORM column collection, but overused where decorators or __init_subclass__ would do.",
    "long": "A metaclass is the class of a class. By default, every class in Python has type as its metaclass; setting metaclass=Meta lets you intercept class creation via Meta.__new__ and Meta.__init__, running code before the class object exists. Common patterns: (1) Registry — automatically register every subclass in a dict for plugin discovery or serializer lookup. (2) ORM column collection — frameworks like Django and SQLAlchemy's legacy declarative system walk the class namespace, find Column/Field descriptors, and assemble a schema attached to the class. (3) Abstract enforcement — ABCMeta uses metaclass machinery to refuse instantiation of classes with unimplemented @abstractmethod members. (4) Attribute validation — reject classes that violate naming conventions or are missing required attributes at definition time, not at first use.\n\nSince Python 3.6, __init_subclass__ and __set_name__ cover the majority of real-world needs without a metaclass — subclass registration, descriptor wiring, and per-class configuration are all simpler with these hooks. Reach for a metaclass only when you need to modify the class namespace before the class is created (e.g. controlling MRO, customizing __prepare__ to use an ordered or specialized mapping, or making isinstance/issubclass lie via __instancecheck__).\n\nMetaclass conflicts are the main practical hazard: multiple inheritance from classes with different metaclasses fails unless one metaclass is a subclass of the other. This frequently breaks when mixing ABC with a framework that has its own metaclass. Document any metaclass clearly, prefer composition with __init_subclass__, and never use a metaclass purely for syntactic sugar — the readability cost for downstream maintainers is significant.",
    "aliases": [
        "metaclass",
        "type subclass",
        "__init_subclass__ alternative",
        "class factories"
    ],
    "tags": [
        "python",
        "metaclasses",
        "advanced",
        "design-patterns",
        "orm"
    ],
    "misconception": "Metaclasses are needed for any 'class-level magic' — in modern Python, __init_subclass__, __set_name__, and class decorators handle most cases and are far easier to read and debug.",
    "why_it_matters": "Metaclasses are powerful but invisible: a class that looks ordinary may behave unexpectedly because its metaclass mutated it at creation time, making debugging and IDE navigation harder. Choosing the lightest tool that works keeps code maintainable.",
    "common_mistakes": [
        "Using a metaclass for subclass registration when __init_subclass__ does the same in five lines.",
        "Mixing classes with incompatible metaclasses and getting cryptic 'metaclass conflict' errors at import time.",
        "Forgetting that metaclass code runs at class definition time, not at instance creation — side effects fire on import.",
        "Returning from Meta.__new__ without calling super().__new__, breaking the type machinery silently.",
        "Using a metaclass purely to add methods that a simple class decorator or mixin would provide more transparently."
    ],
    "when_to_use": [
        "Building an ORM or schema framework that needs to inspect and transform the class namespace at definition time.",
        "Implementing custom isinstance/issubclass behavior via __instancecheck__ and __subclasscheck__.",
        "Enforcing structural invariants on every subclass before the class object becomes accessible.",
        "Customizing __prepare__ to control the namespace mapping used during class body execution."
    ],
    "avoid_when": [
        "A class decorator, mixin, or __init_subclass__ achieves the same result.",
        "The codebase mixes with frameworks (Django, ABC, Pydantic) whose own metaclasses may conflict.",
        "Junior-heavy teams where the indirection cost outweighs any DRY benefit."
    ],
    "related": [
        "py_decorators",
        "py_protocol",
        "py_dataclasses",
        "py_slots"
    ],
    "prerequisites": [
        "py_decorators",
        "py_protocol",
        "py_type_hints"
    ],
    "refs": [
        "https://docs.python.org/3/reference/datamodel.html#metaclasses",
        "https://peps.python.org/pep-3115/",
        "https://peps.python.org/pep-0487/"
    ],
    "bad_code": "# Heavy metaclass just to register subclasses:\nclass PluginMeta(type):\n    registry = {}\n    def __new__(mcs, name, bases, namespace):\n        cls = super().__new__(mcs, name, bases, namespace)\n        if bases:  # skip base class\n            PluginMeta.registry[name] = cls\n        return cls\n\nclass Plugin(metaclass=PluginMeta):\n    pass\n\nclass JsonPlugin(Plugin):\n    pass\n\n# Mixing with ABC fails: metaclass conflict\n# class AbstractPlugin(Plugin, ABC): ...  # TypeError",
    "good_code": "# Modern equivalent with __init_subclass__ — no metaclass needed:\nclass Plugin:\n    registry: dict[str, type] = {}\n\n    def __init_subclass__(cls, **kwargs):\n        super().__init_subclass__(**kwargs)\n        Plugin.registry[cls.__name__] = cls\n\nclass JsonPlugin(Plugin):\n    pass\n\n# Genuine metaclass use: ORM column collection at class creation.\nclass Column:\n    def __set_name__(self, owner, name):\n        self.name = name\n\nclass ModelMeta(type):\n    def __new__(mcs, name, bases, namespace):\n        columns = {k: v for k, v in namespace.items() if isinstance(v, Column)}\n        cls = super().__new__(mcs, name, bases, namespace)\n        cls.__columns__ = columns\n        return cls\n\nclass Model(metaclass=ModelMeta):\n    pass\n\nclass User(Model):\n    id = Column()\n    email = Column()\n# User.__columns__ == {'id': ..., 'email': ...}",
    "quick_fix": "Before writing a metaclass, ask whether __init_subclass__, __set_name__, or a class decorator can do the job — they almost always can.",
    "severity": "medium",
    "effort": "high",
    "created": "2026-05-22",
    "updated": "2026-05-23",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/py_metaclass_patterns",
        "html_url": "https://codeclaritylab.com/glossary/py_metaclass_patterns",
        "json_url": "https://codeclaritylab.com/glossary/py_metaclass_patterns.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Metaclass Patterns](https://codeclaritylab.com/glossary/py_metaclass_patterns) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/py_metaclass_patterns"
            }
        }
    }
}