Advanced 5 min · March 12, 2026

Metaclass __new__ — Database Calls Turn 50ms Import into 3s

Import time jumps 50ms→3s because metaclass __new__ runs DB queries at class definition.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
Quick Answer
  • A metaclass is the class of a class — it controls how classes are created, not how instances behave
  • type is the default metaclass — calling type(name, bases, namespace) is what the interpreter does on every class block
  • The three hooks fire in order: __prepare__ (namespace dict) → class body executes → __new__ (builds class) → __init__ (configures it)
  • Only __new__ can modify or replace the class object — __init__ operates on an already-built class
  • Metaclass conflict occurs when combining two unrelated metaclasses — fix with a merged metaclass that inherits both
  • Reach for __init_subclass__ first — it covers 60% of use cases without the complexity of metaclass MRO chains

Every Python framework you have admired — Django's ORM, SQLAlchemy's declarative base, Python's own enum.Enum — uses metaclasses quietly in the background. They are the reason you can define a Django model by simply inheriting from models.Model and writing plain class attributes, and Django magically maps them to database columns without you calling any setup function. That magic is not magic at all; it is metaclasses intercepting the moment a class is born.

The problem metaclasses solve is class-level enforcement and transformation at definition time, not at runtime. With regular decorators or __init_subclass__, you can react after a class is created. Metaclasses let you intercept the creation process itself — validating attributes, injecting methods, registering classes in a global registry, or enforcing coding standards across an entire class hierarchy the instant the interpreter reads a class block.

Where this matters in 2026: as Python codebases scale into larger teams and plugin-heavy architectures, the cost of inconsistency compounds. A team of 30 engineers cannot rely on everyone remembering to call register() after defining a new plugin class, or to decorate every new model with @validate_schema. Metaclasses make the right thing automatic and the wrong thing impossible. That is a real engineering trade-off worth understanding.

The production concern: metaclasses are powerful but unforgiving. Heavy computation in __new__ runs at import time, not at call time — a database query in a metaclass adds latency to every module import, not just the first use. Metaclass conflicts cause TypeError when combining two frameworks with incompatible metaclasses. And forgetting super() in metaclass hooks silently breaks cooperative multiple inheritance in ways that only surface when two class hierarchies are combined months later. This guide covers both the concepts and the operational patterns that prevent these failures.

How Python Actually Builds a Class — type, __prepare__, __new__, __init__

Before writing a metaclass, you need to understand what happens when the Python interpreter encounters a class block. The process is more mechanical than it looks, and once you see the steps, metaclasses stop being mysterious.

When the interpreter hits a class statement, it does five things in order. First, it resolves which metaclass to use — either the explicit metaclass= keyword argument, the metaclass of the first base class, or type as the default. Second, it calls metaclass.__prepare__(name, bases) to get the namespace dict that the class body will write into — by default this is a plain dict, but you can return anything that implements __setitem__. Third, the class body executes — every assignment, def, and expression runs and writes into that namespace dict. Fourth, metaclass.__new__(mcs, name, bases, namespace) is called with the populated namespace — this is where the actual class object is constructed and returned. Fifth, metaclass.__init__(cls, name, bases, namespace) is called on the class that __new__ just returned — this is where you configure an already-built class.

The critical distinction between __new__ and __init__: __new__ returns the class object, so it is the only hook where you can replace or fundamentally alter what gets built. __init__ receives the class that __new__ already returned — you can add attributes to it, but you cannot change its bases, replace it with a different object, or undo what __new__ did. This distinction catches almost everyone who writes their first metaclass.

The type(name, bases, namespace) three-argument form is not a special function — it is literally the same call path the interpreter uses. Calling type('MyClass', (object,), {'x': 1}) produces a class identical to writing class MyClass: x = 1. Understanding this removes any remaining mystery: metaclasses are just classes whose __new__ and __init__ receive class construction arguments instead of instance construction arguments.

Writing Metaclasses That Actually Solve Real Problems

Theory lands when you see a genuine use case. Three patterns cover the vast majority of legitimate metaclass use in production codebases today.

Pattern 1 — Auto-Registry: Every subclass of a base class is automatically registered in a lookup table the moment it is defined, without any manual register() call. Plugin systems, command-line tool dispatchers, serialisers, and event handler systems all use this. The alternative — requiring developers to manually call register() after every new class — produces bugs whenever someone forgets, and those bugs are silent: the plugin exists, it just is not reachable.

Pattern 2 — Interface Enforcement: Every concrete subclass is guaranteed to implement certain methods at class definition time, not at instantiation time. This catches missing method implementations in CI rather than in production at 2am when a code path is first exercised. The difference between a metaclass and an ABC here is timing: ABCs raise at instantiation, metaclasses raise when the class is defined.

Pattern 3 — Attribute Validation and Transformation: The metaclass intercepts the namespace before the class is frozen. This is how you enforce that all public methods have docstrings, that attribute names follow a naming convention, or that type annotations are present on every method. None of this is possible with __init_subclass__ because by the time __init_subclass__ fires, the class is already built and the namespace is no longer accessible as a mutable dict.

Importantly: always call super() in every hook. Metaclass inheritance chains are fragile, and skipping super() breaks cooperative multiple inheritance silently. The symptom is a class that works in isolation but produces TypeError or wrong behaviour the moment it is combined with another class hierarchy — typically months after the metaclass was written.

Metaclass Conflicts, MRO Pitfalls, and Production Gotchas

This is where intermediate developers become advanced ones: understanding what breaks when metaclasses collide and what to do about it.

The Metaclass Conflict Error: If you try to create a class that inherits from two classes with different, incompatible metaclasses, Python raises TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. This happens in real projects when you combine a Django model (metaclass: ModelBase) with a third-party mixin that uses its own metaclass, or when two libraries that each bring metaclass-based functionality are composed in the same class hierarchy. The fix is a merged metaclass that inherits from both — Python's MRO then chains their __new__ methods cooperatively via super().

Performance at Import Time: Metaclass __new__ runs once per class definition, which happens at import time. The performance cost is not per-instance and not per-call — it is per-import. That means heavy computation in __new__ adds to startup latency and to pytest collection time, both of which compound as the codebase grows. Profile with python -X importtime your_module.py before and after adding any metaclass to a high-import-count module.

__prepare__ and Custom Namespaces: The __prepare__ hook returns the dict-like object that the class body writes into. Since Python 3.7 this is an ordered dict by default, so __prepare__ is only needed when you want non-dict semantics — for example, a namespace that raises on duplicate attribute names (Python normally overwrites silently) or a namespace that type-checks entries as they are assigned.

MRO Order in Merged Metaclasses: When you write class MergedMeta(MetaA, MetaB): pass, the MRO determines the order in which __new__ and __init__ fire. Always put the more specific or more critical metaclass first. Reversing the order can cause one metaclass's transformations to be undone or overwritten by the other, producing behaviour that is correct in unit tests but wrong when both metaclasses are active.

Metaclass vs __init_subclass__
FeatureMetaclass__init_subclass__
Fires for the base class itselfYes — __new__ fires for the class that declares metaclass=No — only fires for classes that inherit from the defining class
Custom namespace (__prepare__)Yes — full control over the dict the class body writes intoNo — the namespace is always a plain dict
ComplexityHigh — three hooks, MRO considerations, conflict risk when composing with other metaclassesLow — just a classmethod on the base class, familiar to any intermediate Python developer
Modify class attributes before freezeYes — in __new__ before super() returns the class objectLimited — class is already built when __init_subclass__ fires
Metaclass conflict riskYes — combining two unrelated metaclasses requires a merged metaclassNone — no metaclass involved, no conflict possible
Typical use casesORMs, enum-like systems, plugin registries that need namespace control or base-class interceptionSubclass validation, auto-registration, simple enforcement where the base class already exists
Readability for teamLow — steep learning curve, requires understanding of __prepare__, __new__, __init__ and their orderHigh — familiar classmethod syntax, behaviour is obvious from the method name
Performance costOne-time at import and class-definition time — __new__ never runs again after the class is builtOne-time at subclass definition time — equivalent cost, no additional overhead

Key Takeaways

  • A metaclass is the class of a class — type is the default metaclass, and calling type(name, bases, namespace) is literally what the interpreter does on every class block. Understanding this removes all mystery from metaclass behaviour.
  • The four hooks fire in strict order: __prepare__ returns the namespace dict, the class body executes into it, __new__ builds and returns the class object, __init__ configures the already-built class. Only __new__ can replace or fundamentally alter the class — __init__ cannot.
  • Three patterns cover 90% of legitimate metaclass use: auto-registry (every subclass registers itself), interface enforcement (missing methods caught at definition time), and attribute validation (docstrings, annotations, naming conventions enforced before the class is frozen).
  • Always call super() with the correct signature in every hook — missing super() silently breaks cooperative multiple inheritance in ways that only surface when two class hierarchies are composed, often months after the metaclass was written.
  • Metaclass __new__ runs at import time, not at call time — never perform I/O, database queries, or heavy computation there. Profile with python -X importtime before and after adding any metaclass to a high-import module.
  • Reach for __init_subclass__ before writing a metaclass — it handles the majority of subclass registration and validation use cases with no MRO complexity and no conflict risk. Reserve metaclasses for __prepare__, base-class interception, or framework-level class construction where the capability genuinely justifies the maintenance cost.

Common Mistakes to Avoid

  • Forgetting to call super() in metaclass __new__ or __init__
    Symptom: Incomplete class object, missing attributes, or TypeError: object.__init_subclass__() takes no keyword arguments. The class works in isolation but fails when combined with other class hierarchies — often months after the metaclass was written.
    Fix: Always chain super().__new__(mcs, name, bases, namespace) in __new__ and super().__init__(name, bases, namespace) in __init__ as the first or last call in every hook. Metaclass cooperative inheritance depends entirely on super() chaining — a missing super() silently breaks the MRO chain for every class that uses the metaclass.
  • Performing heavy work inside __new__ that runs at import time
    Symptom: Test suite becomes mysteriously slow — 200ms added per test file during collection. Application startup takes seconds instead of milliseconds. Profiling with python -X importtime shows metaclass __new__ as the top consumer during module import.
    Fix: Defer expensive operations to first use with lazy initialisation, or to an explicit validate() classmethod that CI calls once per build. Profile before and after adding any non-trivial logic to __new__ — import time compounds across every module and every test run.
  • Assuming metaclass= on a subclass cleanly overrides the parent's metaclass
    Symptom: TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. Most commonly occurs when composing a Django model with a third-party mixin, or when two library metaclasses meet for the first time in a combining class.
    Fix: Create a merged metaclass with class MergedMeta(ParentMeta, NewMeta): pass and specify metaclass=MergedMeta on the combining class. Python's MRO chains both __new__ and __init__ cooperatively via super(). Verify the resolution with type(CombinedClass).__mro__.
  • Modifying bases or namespace in __init__ instead of __new__
    Symptom: Changes to bases or namespace have no effect on the class — the class is already constructed by the time __init__ runs. The code looks correct, executes without error, and does nothing.
    Fix: Move all base class or namespace modifications to __new__, before the super().__new__ call returns the class object. __init__ can add attributes to an already-built class but cannot change what it fundamentally is.
  • Not passing **kwargs through metaclass hooks when supporting keyword arguments in class definitions
    Symptom: TypeError: __new__() got an unexpected keyword argument when a subclass uses class MyClass(Base, some_option=True). The metaclass does not forward keyword arguments through the super() chain.
    Fix: Add kwargs to every metaclass hook signature: def __new__(mcs, name, bases, namespace, kwargs) and def __init__(cls, name, bases, namespace, kwargs). Pass kwargs to super() calls so keyword arguments flow correctly through the entire MRO chain. This is required in Python 3.6+ when any class in the hierarchy uses keyword arguments at class definition time.

Interview Questions on This Topic

  • QWhat is a metaclass in Python, and how does it differ from a regular class decorator?Mid-levelReveal
    A metaclass is the class of a class — it controls how class objects are constructed, not how instances behave. When the interpreter encounters a class block, it calls the metaclass with three arguments: the class name, the tuple of base classes, and the namespace dict populated by the class body. The metaclass's __new__ and __init__ hooks fire at class definition time — once, when the module is imported — not at instantiation time. A class decorator, by contrast, receives an already-built class object and can wrap or modify it, but it fires after the class is fully constructed and cannot influence the construction process itself. The practical difference: a metaclass can customise the namespace the class body writes into (via __prepare__), intercept the base class definition, and prevent the class from being built at all. A decorator can only transform the class after it exists.
  • QExplain the order in which __prepare__, __new__, and __init__ fire during class creation, and what each one can do that the others cannot.SeniorReveal
    __prepare__ fires first, before the class body executes, and returns the dict-like object that the class body writes into. It is the only hook that can customise the namespace — returning a custom dict subclass allows you to detect duplicate attributes, enforce ordering, or type-check entries as they are assigned. __new__ fires after the class body has executed and receives the populated namespace. It constructs and returns the class object — it is the only hook that can replace the class with a different object or fundamentally alter the class's bases before construction. __init__ fires after __new__ returns the class object and receives the already-built class. It can configure the class — add attributes, register it in a dict — but it cannot change the class object itself, replace it, or modify its bases retroactively. The practical rule: use __prepare__ for namespace customisation, __new__ for class construction and transformation, __init__ for post-construction configuration.
  • QHow would you resolve a metaclass conflict when combining two classes that use different incompatible metaclasses?SeniorReveal
    Python raises TypeError: metaclass conflict when a derived class would have a metaclass that is not a subclass of all its bases' metaclasses. The resolution is to create a merged metaclass that inherits from both conflicting metaclasses: class MergedMeta(MetaA, MetaB): pass. Python's MRO then chains MetaA.__new__ to MetaB.__new__ to type.__new__ cooperatively via super(), assuming all hooks call super() correctly. Specify metaclass=MergedMeta on the combining class. Verify the resolution with type(CombinedClass).__mro__ — both MetaA and MetaB should appear. The order in the merged metaclass's inheritance tuple matters: put the more specific or higher-priority metaclass first, since it will run its __new__ first in the MRO chain.
  • QWhen would you choose __init_subclass__ over a metaclass, and what can a metaclass do that __init_subclass__ cannot?SeniorReveal
    __init_subclass__ is a classmethod defined on a base class that fires whenever a subclass is defined. It handles registration, validation, and simple enforcement with far less complexity than a metaclass — no MRO conflicts, no __new__ or __init__ signatures to get right, readable to any intermediate Python developer. Choose __init_subclass__ when the use case is subclass-only (not the defining base class itself) and does not require namespace customisation. A metaclass provides three capabilities __init_subclass__ cannot: it fires for the base class itself via its own __new__ and __init__; it provides __prepare__, the only way to customise the namespace dict the class body writes into; and it can return a completely different object from __new__ instead of a class — enabling enum-like systems, singleton factories, and ORM column mapping. The heuristic: if __init_subclass__ solves the problem, use it. Reach for a metaclass only when you need __prepare__, base-class interception, or framework-level class construction control.
  • QWhy does heavy computation in a metaclass __new__ affect pytest collection time, and how do you diagnose and fix it?SeniorReveal
    Metaclass __new__ runs once per class definition, at import time — not per-instance and not per-call. pytest discovers tests by importing every test module, which imports every module those test modules depend on. If any of those modules define classes that trigger a heavy __new__ — a database query, a network call, or expensive reflection — that cost is paid on every import, multiplied by the number of test files that transitively import the affected module. The diagnosis: run python -X importtime pytest_module.py 2>&1 | sort -k2 -rn | head -20 and look for modules with high self time. The fix: move any I/O or heavy computation out of __new__ into a lazy initialiser that runs on first use, or into an explicit validate() classmethod that CI calls once per build rather than on every import. The symptom that distinguishes this from a runtime bottleneck is that the slowness appears during pytest collection, before any test executes.

Frequently Asked Questions

What is a Python metaclass in simple terms?

A metaclass is the class of a class. In Python, everything is an object — including classes. A class object is an instance of its metaclass, just as a string object is an instance of str. The default metaclass is type, which is what the interpreter uses when it encounters any class block. When you write a custom metaclass, you are customising what happens when a class is created — not when an instance of that class is created. The practical effect: you can automatically add methods, validate attributes, register the class in a global dict, or reject the class entirely, all at the moment the interpreter reads the class definition.

When should I use a metaclass instead of a class decorator?

Use a class decorator when you want to transform or wrap a class after it is fully built. Decorators are simpler, more readable, and sufficient for the majority of class-level customisation. Use a metaclass when you need to intercept the construction process itself — specifically when you need __prepare__ to customise the namespace the class body writes into, when you need to intercept the base class definition (not just subclasses), or when you need to prevent the class from being created at all based on its definition. The other case for metaclasses: when the customisation must apply automatically to every subclass without any action from the subclass author.

How do I fix a metaclass conflict TypeError?

Create a merged metaclass that inherits from both conflicting metaclasses: class MergedMeta(MetaA, MetaB): pass. Then use metaclass=MergedMeta on the class where the conflict occurs. Python's MRO chains both metaclasses' __new__ and __init__ methods cooperatively via super(), assuming both metaclasses call super() correctly. Verify the fix with type(YourCombinedClass).__mro__ — both MetaA and MetaB should appear in the chain. The most common source of this error in 2026 is composing two libraries that each bring a metaclass — Django's ModelBase and a third-party validation metaclass being the classic example.

What is __prepare__ and when do I need it?

__prepare__ is a classmethod on a metaclass that fires before the class body executes. It returns the dict-like object that the class body writes into. Since Python 3.7, the default dict is insertion-ordered, so you only need __prepare__ when you want non-dict behaviour: a namespace that raises on duplicate attribute definitions, a namespace that type-checks entries as they are assigned, or a namespace that maintains a specific ordered structure beyond what a plain dict provides. If you are not customising the namespace, skip __prepare__ — the default is sufficient for the vast majority of metaclass use cases.

Do subclasses need to specify metaclass= if their parent already uses one?

No — metaclasses are inherited automatically. If Base has metaclass=YourMeta, every class that inherits from Base also uses YourMeta without any additional declaration. Repeating metaclass=YourMeta on the subclass is harmless if it is the same metaclass, but it signals a misunderstanding and should be removed to keep the code clear. If a subclass specifies a different metaclass, that new metaclass must be a subclass of the parent's metaclass — otherwise Python raises the metaclass conflict TypeError.

🔥

That's Advanced Python. Mark it forged?

5 min read · try the examples if you haven't

Previous
Coroutines and asyncio in Python
3 / 17 · Advanced Python
Next
Memory Management in Python