A metaclass is the class of a class. The same way an instance is built from a class, a class itself is built from a metaclass, and that metaclass is type by default. Metaclasses give you a hook to run code at the moment a class is defined, before any instance ever exists. This lesson covers what they are, how to write one, when __init_subclass__ is the better choice, and three practical patterns from the e-commerce world: registering product types, enforcing invariants on Order subclasses, and auto-building __slots__ from declared fields.
Every Python value is an object, and every object has a class. That rule does not stop at instances. The class itself is an object, and its class is a metaclass.
Three things to read out of that output. The class of an instance is its class (Product). The class of a class is type. And the class of type is type itself, which closes the loop so the chain doesn't go on forever.
The relationship is easier to see as a diagram:
The arrow labelled "type of" reads "is an instance of". p is an instance of Product. Product is an instance of type. type is an instance of itself.
This isn't a curiosity. Because a class is an instance of type, you can do the same things to a class that you'd do to any object: pass it as an argument, store it in a list, attach attributes to it, and ask type to build a new one for you on the fly.
type as a Class Constructortype has two completely different jobs depending on how you call it. With one argument, it returns the class of that argument (the form you saw above). With three arguments, it builds a new class.
The three-argument form looks like this:
name is a string, the new class's __name__.bases is a tuple of base classes.namespace is a dict of the class body: methods, class attributes, everything.Here's the same Product class written twice, once with the class statement and once by calling type directly:
The two classes are equivalent. The class statement is sugar. Behind the scenes, Python collects the class body into a dict, decides which metaclass to use (defaulting to type), and calls the metaclass with the three arguments above. The result is bound to the name on the left.
That's the entire idea of a metaclass: it's the callable Python uses to build a class. Replace it with something else and you get to customize how classes are built.
Cost: The three-argument type(...) form is a real call, so building hundreds of classes at runtime is slower than importing one module that defines them statically. For ten or twenty plugin types you'll never notice. For tens of thousands, you would.
To make a custom metaclass, subclass type and override __new__, __init__, or both. Then tell a class to use it with the metaclass= keyword on the class line.
The hooks fire in this order:
__new__ creates the class object and returns it. __init__ runs after, on the already-built object. For most use cases either one works, but __new__ is the right hook when you need to modify the namespace before the class is built (for example, adding methods or changing attributes).
A trivial first example, a metaclass that just announces itself:
Two things to read here. First, LoudMeta.__new__ ran before print("Class is built."), which means the metaclass executed at class-definition time, not when an instance was created. Second, the namespace had three entries even though the class body only shows two visible ones (category, label). The extra one is __qualname__ (and the count can include __module__ depending on the version), which Python adds automatically when building a class.
A slightly more useful metaclass that adds an attribute to every class it builds:
Both classes got created_at injected into their namespace before they were built. Notice the change happened in the dict, not on the class object after the fact. That's why __new__ is the natural hook for "modify what the class will contain"; by the time __init__ runs, the class object is already a real class and adding attributes to it is just Class.attr = value.
Metaclasses don't follow the same inheritance rules as ordinary classes. If you subclass a class that uses a custom metaclass, the subclass uses the same metaclass automatically. You don't repeat metaclass= on every subclass.
Book and Headphones never said metaclass=RegisteredMeta, but they got it because Python computes the metaclass of a new class as "the most derived metaclass among the bases". Product's metaclass is RegisteredMeta, so its children inherit it. Notice the if bases: guard inside __new__, that's how the metaclass avoids registering Product itself (it has no bases listed, so bases is empty for it under class Product(metaclass=...) with no parent). For classes that explicitly list Product as a base, bases is non-empty and the class is registered.
The second print confirms that type(Book) is indeed RegisteredMeta, not type. The metaclass propagates through inheritance.
There's a flip side to that rule, and it bites the first time you hit it. If a class tries to inherit from two parents with incompatible metaclasses, Python raises TypeError:
The fix is to write a metaclass that inherits from both MetaA and MetaB and use that one explicitly:
This is the single most common reason developers swear off metaclasses. Libraries that use metaclasses for their own purposes (ORMs, ABCs, Pydantic v1) compose badly with each other when you try to mix them. If you can solve the problem with __init_subclass__ instead, do that.
__init_subclass__, the Lighter AlternativeA lot of what people reach for metaclasses for can be done with __init_subclass__, a regular class method (technically a class-bound hook) that Python calls when a subclass is created. It's been in the language since Python 3.6 and it covers maybe 80 percent of real metaclass use cases.
The plugin registry from earlier, rewritten without a metaclass:
Same behaviour, no metaclass. Product itself is not added to its own registry because __init_subclass__ fires only when a _sub_ class is created, not when the base class is. Every subclass triggers the hook automatically.
__init_subclass__ can accept keyword arguments passed on the class line. That gives a clean way to configure the registration per-subclass:
That code="BOOK" on the class line is passed straight to __init_subclass__. No metaclass, no magic, and a class that forgets to set a code raises a clean TypeError at definition time instead of failing later.
When should you reach for a metaclass instead of __init_subclass__?
| Use case | Tool |
|---|---|
| Register subclasses in a list or dict | __init_subclass__ |
| Validate that subclasses define certain methods | __init_subclass__ |
| Add or rename attributes on the subclass after creation | __init_subclass__ |
| Modify the namespace dict before the class is built | metaclass __new__ |
Customize isinstance / issubclass checks | metaclass with __instancecheck__ / __subclasscheck__ |
Customize attribute lookup at the class level (SomeClass.foo) | metaclass with __getattr__ |
| Make the class itself callable in a non-standard way | metaclass with __call__ |
The short version: if you only need to react _after_ the class is built, __init_subclass__ is enough. If you need to change the class _during_ creation or override how the class object itself behaves, you need a metaclass.
The e-commerce version of "auto-register subclasses" is a product-type registry. An online store has many product types (book, headphones, t-shirt), and the catalogue layer wants to look one up by a short code without writing a long if/elif chain.
Using a metaclass:
The metaclass does two useful things at definition time. It enforces that every subclass declare a code, raising a clear error if one is missing. And it adds the class to the registry. The factory function make_product then looks up the right class by code and constructs an instance, no if/elif chain in sight.
The exact same registry built with __init_subclass__:
For "just register things", the second version is shorter, easier to read, and doesn't introduce a metaclass that might conflict with other libraries. The metaclass version is only worth the cost if you also need behaviour that __init_subclass__ can't give you.
Order ClassesSometimes you want to check, at class-definition time, that subclasses satisfy a rule. A common one in e-commerce: every order type must implement a total() method, and a currency class attribute. A typo in either of those should fail loudly when the module loads, not silently at runtime in production.
A metaclass that enforces this:
If you misspell the method name as totl, you don't get a quiet AttributeError at checkout. You get a TypeError the moment Python imports the file:
That's the whole point. Fast feedback on a misconfigured class beats a runtime error at the worst possible moment.
The same enforcement with __init_subclass__:
Either approach works. The __init_subclass__ version is one line shorter and doesn't carry the metaclass risk; that's why it's the default choice for this kind of "check that subclasses are well-formed" job. Reach for a metaclass only if you also need to mutate the class's namespace before it's built, which is the next pattern.
__slots__ from Declared FieldsThis is the case where a metaclass really earns its keep. A regular Python class stores instance attributes in a per-instance __dict__. For classes that hold a lot of objects in memory (think a million Product rows loaded from a database), declaring __slots__ cuts memory use significantly by replacing the dict with a fixed array.
Hand-writing __slots__ is tedious and easy to forget when you add a new field. A metaclass can read the declared field names from a fields attribute and build __slots__ automatically:
Because the metaclass wrote __slots__ into the namespace _before_ the class was built, the class is a real slotted class. The instance has no __dict__, attempting to set an undeclared attribute raises AttributeError, and the per-instance memory footprint is much smaller than the default. None of that would work with __init_subclass__, because by the time __init_subclass__ runs, the class is already built and __slots__ has already been frozen into its layout.
This is the rule of thumb for choosing a metaclass: you need to modify the namespace dict before class creation, and a post-hoc hook is too late.
Metaclasses also compose well with descriptors. A descriptor is an object that controls how an attribute is read or written, and a metaclass is a natural place to collect them. The pattern looks like "scan the namespace for descriptors, register them on the class by their attribute name, and maybe build __slots__ from their names."
A few traps that catch nearly everyone the first time. Each one has a fix.
Forgetting to call `super().__new__`. A custom __new__ has to actually build the class object. If you forget the super() call, your metaclass returns None (or whatever you wrote) and Python is unhappy.
The fix is to always return super().__new__(mcs, name, bases, namespace). Same shape every time.
Metaclass conflict with libraries. Libraries like abc.ABCMeta and Pydantic v1 use their own metaclasses. Mixing them with your own requires a combined metaclass:
The combined metaclass inherits from both so neither parent loses its behaviour. Note that PaymentMethod itself isn't in the registry because the metaclass guards on if bases: (and (ABC,) _is_ non-empty, so PaymentMethod actually would land in the registry with this implementation). Tighten the guard if you need to skip abstract bases too: check whether the class has any @abstractmethod left.
Debugging is harder. Stack traces involving metaclasses are confusing because the metaclass executes during import. A subtle bug in __new__ can prevent a module from even loading, and the traceback points at the class line rather than the failing logic. Keep custom metaclasses small, give them a clear name (RegistryMeta, not Meta), and add unit tests that try defining a class with the metaclass directly using type(name, bases, namespace) so failures are isolated.
Confusing `__new__` with `__init__`. Both fire when a class is created. __new__ runs first and is responsible for producing the class object. __init__ runs after, on the already-built class. Use __new__ when you need to change the namespace or substitute a different class object. Use __init__ when you only need to record or wire up the finished class. If you're not sure, __new__ is the safe default.
This is the most important question in the lesson. Tim Peters put it best: "Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't." The reason behind that line isn't gatekeeping, it's that most uses of metaclasses are better served by simpler tools.
A decision tree:
The flow boils down to: prefer __init_subclass__ or a class decorator if the work happens after the class is built. Reach for a metaclass only when you need to modify the class body's namespace before construction, customize isinstance/issubclass, intercept attribute lookup on the class itself, or change what calling the class does (MyClass()). Even then, ask whether a single shared metaclass in the codebase is enough; you rarely need more than one.
Real-world uses you'll see in popular libraries:
abc.ABCMeta uses a metaclass to track abstract methods and prevent instantiation of incomplete subclasses.ModelMeta reads class-body declarations and builds an internal _meta object plus form/admin integrations.ModelMetaclass collects field definitions, builds validators, and constructs the schema.enum.EnumMeta makes Color.RED return a singleton and forbids reassigning members.In every case, the metaclass is doing something that can't be done after the class is built. That's the bar.
10 quizzes