Metaclasses: The Classes That Make Classes
type is Python's default metaclass — the thing that builds every class you write. Learn to read metaclass code and when __init_subclass__ makes them unnecessary.
I finally opened Amir's shared library this morning to understand why the order processors are structured the way they are. There's this class called ProcessorMeta and I have genuinely no idea what it does. It has type in the inheritance list — which doesn't make sense to me — and a __new__ method that's building a dictionary. It's not a data class. It's not a regular base class. I stared at it for twenty minutes.
You stared at a metaclass. The reason it looks wrong is that it is a different kind of thing — not an instance of a class, not a subclass of your domain objects. It's the machinery that makes classes. Before we look at Amir's version, let me show you what you already know that explains it.
Yesterday we did type() with three arguments — type('OrderBatch', (object,), {'process': ...}) — and it created a class at runtime. Is that connected?
Directly. When Python sees a class statement, it doesn't just allocate memory and move on. It calls type() with the class name, the base classes, and a namespace dictionary. type is Python's default metaclass — the class that makes classes. Every class you have ever written was built by type unless you said otherwise.
So type is both the function I use to check what something is — type(42) returns int — and the thing that creates classes?
Both, yes. Python's designers were maximally economical with the name and maximally confusing to everyone who encounters it for the first time. type(42) asks "what kind of thing is this?" type('Foo', (), {}) says "build me a new class." Same name, two jobs.
Okay. So a metaclass is... a class whose instances are classes? Not instances of my domain objects, but the class objects themselves?
That's exactly it. Here's the theater analogy made precise. A regular class is like a theater production — the director gives actors roles, the stage has a design, the show runs. An instance is a single performance of that show. But a metaclass is the theater company itself — the organization that decides how productions are structured, what every show must have, and what happens when a new show is created. The theater company doesn't perform. It makes performers.
Let's look at the pieces:
# type is the metaclass for every class you write
print(type(int)) # <class 'type'>
print(type(list)) # <class 'type'>
print(type(str)) # <class 'type'>
class Order:
pass
print(type(Order)) # <class 'type'>
Every class is an instance of type. The class Order is an object — an instance of the metaclass type. This is not a trick. Classes are first-class objects in Python, and type is their class.
So when I write class Order: — Python is internally doing something like Order = type('Order', (object,), {})?
Precisely. The class statement is syntactic sugar. Python collects the class body into a namespace dictionary, then calls the metaclass with the name, bases, and namespace. If you want to customize what happens at that moment — every time a subclass is created — you hook into that process.
Here's a minimal custom metaclass:
class RegistryMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
print(f"Class created: {name}")
return cls
class BaseProcessor(metaclass=RegistryMeta):
pass
class StandardProcessor(BaseProcessor):
pass
# prints: Class created: BaseProcessor
# prints: Class created: StandardProcessor
RegistryMeta.__new__ is called every time a class using this metaclass is defined — including the base class itself. mcs is the metaclass. name is the string 'StandardProcessor'. bases is (BaseProcessor,). namespace is the class body dictionary.
I understand what __new__ is doing mechanically — it's calling the parent to actually build the class — but the mcs parameter is confusing me. In a normal class, __new__ receives the class itself as the first argument. Here it's receiving... what exactly?
The metaclass. In a normal class, __new__(cls, ...) receives the class being instantiated. In a metaclass, __new__(mcs, name, bases, namespace) receives the metaclass — the thing doing the creating. mcs is RegistryMeta. It's the theater company receiving the script, deciding how to produce it, and returning the finished production. The convention is mcs to distinguish it from cls, which by convention refers to a regular class.
Amir's metaclass is building a dictionary — REGISTRY — and adding each new class to it. That's what it's doing. Every time a new processor class is defined, the metaclass catches it and stores it in a lookup table.
You just read Amir's metaclass. That is the canonical metaclass use case: auto-registering subclasses. Here's the full pattern:
class RegistryMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Don't register the base class itself
if bases: # bases is empty tuple for the root class
if not hasattr(mcs, '_registry'):
mcs._registry = {}
mcs._registry[name] = cls
return cls
class BaseProcessor(metaclass=RegistryMeta):
pass
class StandardProcessor(BaseProcessor):
def process(self, order):
return f"standard: {order}"
class PriorityProcessor(BaseProcessor):
def process(self, order):
return f"priority: {order}"
print(RegistryMeta._registry)
# {'StandardProcessor': <class 'StandardProcessor'>,
# 'PriorityProcessor': <class 'PriorityProcessor'>}
Now you can load a processor by name at runtime: RegistryMeta._registry['PriorityProcessor']. That's what Amir was building — a plugin system where adding a new processor subclass automatically registers it.
Okay. I can read it. I could not have written it this morning and I'm not confident I could write it tomorrow. But I can read it. My question is: why didn't Amir just use __init_subclass__? We have that in Python 3.6+. It does exactly this without the metaclass ceremony.
Sharp. That is the right question to ask. __init_subclass__ was added specifically to eliminate the most common metaclass use case. Here's the same registry pattern without a metaclass:
class BaseProcessor:
REGISTRY = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
BaseProcessor.REGISTRY[cls.__name__] = cls
class StandardProcessor(BaseProcessor):
def process(self, order):
return f"standard: {order}"
class PriorityProcessor(BaseProcessor):
def process(self, order):
return f"priority: {order}"
print(BaseProcessor.REGISTRY)
# {'StandardProcessor': <class 'StandardProcessor'>,
# 'PriorityProcessor': <class 'PriorityProcessor'>}
__init_subclass__ is called automatically on the base class every time a subclass is defined. cls inside it is the new subclass — not BaseProcessor. Same result. No metaclass. Readable by anyone who knows Python 3.
The first time you use a metaclass where __init_subclass__ would do, a senior engineer somewhere winces.
I was going to say that. You beat me to it. Yes — that is the rule. __init_subclass__ handles "do something every time a subclass is defined." That covers auto-registration, validation of subclass structure, setting default attributes. Metaclasses are for things __init_subclass__ cannot do: modifying the class creation process itself, changing how the class namespace is constructed before the class body runs, or creating class hierarchies that need to interoperate with type at a lower level. That is a short list.
What's an example of something only a metaclass can do?
Controlling the class namespace before the body executes. Python lets a metaclass return a custom dictionary from __prepare__ that collects the class body — so you can intercept attribute definitions as they happen, in order. __init_subclass__ only fires after the class is fully built. The enum.EnumMeta in the standard library uses exactly this to remember the order that enum members were defined and to prevent duplicate names. You cannot do that with __init_subclass__ because by the time it's called, the class is already assembled.
The other case: when you genuinely need to inject or override type-level behavior — descriptor lookup, isinstance checks, custom __call__ on the class itself. These are real use cases. They're also rare enough that most Python engineers never need to write one.
So Amir's metaclass is technically correct but probably unnecessary. He could have used __init_subclass__. I'm not going to tell him that.
Tell him. That's a legitimate code review comment — "could this be __init_subclass__? Same behavior, more readable." He might have a reason. He might not. Either way, you know enough now to have the conversation.
One thing I'm not clear on: when Amir defines a new processor in a different file, it gets registered automatically — just by importing the file that defines the class. Is that how it works?
Exactly. Class definition happens at import time in Python. The moment Python executes class StandardProcessor(BaseProcessor):, __init_subclass__ fires and the class lands in the registry. If that file is imported — even just imported, never explicitly registered — the processor is available. This is why plugin systems work: you import a module, the classes in it register themselves, and the lookup table is populated. No explicit registration call needed.
So the order routing code can do processor = BaseProcessor.REGISTRY['StandardProcessor']() and it doesn't need to know the class exists ahead of time. You add a new processor file, import it, and the routing code just works. That's why Amir built it this way.
You just described the architecture from first principles. That's not following a pattern — that's understanding why the pattern exists.
I want to make sure I haven't convinced myself I understand more than I do. What would actually break if someone wrote class BadProcessor: — no inheritance — and expected it to be in the registry?
Nothing breaks — it just doesn't register. __init_subclass__ only fires when you subclass BaseProcessor. A class that doesn't inherit from BaseProcessor never triggers it. The registry only knows about classes that declared the inheritance. This is the contract: you join the system by subclassing. The mechanism is transparent but it requires the subclass relationship.
Okay. Let me be honest about where I am: I still wouldn't write a metaclass tomorrow. But I opened Amir's ProcessorMeta this morning and it was incomprehensible. Right now I could explain it to someone. I know what mcs is. I know why __new__ gets called at class definition time, not at instance creation time. I know that __init_subclass__ is almost always the right tool instead. That's real.
That's exactly where Day 7 should land. Metaclasses are one of Python's most powerful and most misused features. Understanding them well enough to read them, evaluate whether they were the right choice, and know the modern alternative — that is the professional relationship with metaclasses. Writing one is a last resort, not a flex.
Next week we leave the object model entirely. The pipeline you've been looking at is slow not because of how objects are structured — it's slow because it runs one thing at a time. We're going to look at what it actually means for Python code to run fast: threads, processes, and async. The question isn't "what makes a class" anymore. It's "what makes a thousand orders process in half a second instead of eight."
Practice your skills
Sign up to write and run code in this lesson.
Metaclasses: The Classes That Make Classes
type is Python's default metaclass — the thing that builds every class you write. Learn to read metaclass code and when __init_subclass__ makes them unnecessary.
I finally opened Amir's shared library this morning to understand why the order processors are structured the way they are. There's this class called ProcessorMeta and I have genuinely no idea what it does. It has type in the inheritance list — which doesn't make sense to me — and a __new__ method that's building a dictionary. It's not a data class. It's not a regular base class. I stared at it for twenty minutes.
You stared at a metaclass. The reason it looks wrong is that it is a different kind of thing — not an instance of a class, not a subclass of your domain objects. It's the machinery that makes classes. Before we look at Amir's version, let me show you what you already know that explains it.
Yesterday we did type() with three arguments — type('OrderBatch', (object,), {'process': ...}) — and it created a class at runtime. Is that connected?
Directly. When Python sees a class statement, it doesn't just allocate memory and move on. It calls type() with the class name, the base classes, and a namespace dictionary. type is Python's default metaclass — the class that makes classes. Every class you have ever written was built by type unless you said otherwise.
So type is both the function I use to check what something is — type(42) returns int — and the thing that creates classes?
Both, yes. Python's designers were maximally economical with the name and maximally confusing to everyone who encounters it for the first time. type(42) asks "what kind of thing is this?" type('Foo', (), {}) says "build me a new class." Same name, two jobs.
Okay. So a metaclass is... a class whose instances are classes? Not instances of my domain objects, but the class objects themselves?
That's exactly it. Here's the theater analogy made precise. A regular class is like a theater production — the director gives actors roles, the stage has a design, the show runs. An instance is a single performance of that show. But a metaclass is the theater company itself — the organization that decides how productions are structured, what every show must have, and what happens when a new show is created. The theater company doesn't perform. It makes performers.
Let's look at the pieces:
# type is the metaclass for every class you write
print(type(int)) # <class 'type'>
print(type(list)) # <class 'type'>
print(type(str)) # <class 'type'>
class Order:
pass
print(type(Order)) # <class 'type'>
Every class is an instance of type. The class Order is an object — an instance of the metaclass type. This is not a trick. Classes are first-class objects in Python, and type is their class.
So when I write class Order: — Python is internally doing something like Order = type('Order', (object,), {})?
Precisely. The class statement is syntactic sugar. Python collects the class body into a namespace dictionary, then calls the metaclass with the name, bases, and namespace. If you want to customize what happens at that moment — every time a subclass is created — you hook into that process.
Here's a minimal custom metaclass:
class RegistryMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
print(f"Class created: {name}")
return cls
class BaseProcessor(metaclass=RegistryMeta):
pass
class StandardProcessor(BaseProcessor):
pass
# prints: Class created: BaseProcessor
# prints: Class created: StandardProcessor
RegistryMeta.__new__ is called every time a class using this metaclass is defined — including the base class itself. mcs is the metaclass. name is the string 'StandardProcessor'. bases is (BaseProcessor,). namespace is the class body dictionary.
I understand what __new__ is doing mechanically — it's calling the parent to actually build the class — but the mcs parameter is confusing me. In a normal class, __new__ receives the class itself as the first argument. Here it's receiving... what exactly?
The metaclass. In a normal class, __new__(cls, ...) receives the class being instantiated. In a metaclass, __new__(mcs, name, bases, namespace) receives the metaclass — the thing doing the creating. mcs is RegistryMeta. It's the theater company receiving the script, deciding how to produce it, and returning the finished production. The convention is mcs to distinguish it from cls, which by convention refers to a regular class.
Amir's metaclass is building a dictionary — REGISTRY — and adding each new class to it. That's what it's doing. Every time a new processor class is defined, the metaclass catches it and stores it in a lookup table.
You just read Amir's metaclass. That is the canonical metaclass use case: auto-registering subclasses. Here's the full pattern:
class RegistryMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Don't register the base class itself
if bases: # bases is empty tuple for the root class
if not hasattr(mcs, '_registry'):
mcs._registry = {}
mcs._registry[name] = cls
return cls
class BaseProcessor(metaclass=RegistryMeta):
pass
class StandardProcessor(BaseProcessor):
def process(self, order):
return f"standard: {order}"
class PriorityProcessor(BaseProcessor):
def process(self, order):
return f"priority: {order}"
print(RegistryMeta._registry)
# {'StandardProcessor': <class 'StandardProcessor'>,
# 'PriorityProcessor': <class 'PriorityProcessor'>}
Now you can load a processor by name at runtime: RegistryMeta._registry['PriorityProcessor']. That's what Amir was building — a plugin system where adding a new processor subclass automatically registers it.
Okay. I can read it. I could not have written it this morning and I'm not confident I could write it tomorrow. But I can read it. My question is: why didn't Amir just use __init_subclass__? We have that in Python 3.6+. It does exactly this without the metaclass ceremony.
Sharp. That is the right question to ask. __init_subclass__ was added specifically to eliminate the most common metaclass use case. Here's the same registry pattern without a metaclass:
class BaseProcessor:
REGISTRY = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
BaseProcessor.REGISTRY[cls.__name__] = cls
class StandardProcessor(BaseProcessor):
def process(self, order):
return f"standard: {order}"
class PriorityProcessor(BaseProcessor):
def process(self, order):
return f"priority: {order}"
print(BaseProcessor.REGISTRY)
# {'StandardProcessor': <class 'StandardProcessor'>,
# 'PriorityProcessor': <class 'PriorityProcessor'>}
__init_subclass__ is called automatically on the base class every time a subclass is defined. cls inside it is the new subclass — not BaseProcessor. Same result. No metaclass. Readable by anyone who knows Python 3.
The first time you use a metaclass where __init_subclass__ would do, a senior engineer somewhere winces.
I was going to say that. You beat me to it. Yes — that is the rule. __init_subclass__ handles "do something every time a subclass is defined." That covers auto-registration, validation of subclass structure, setting default attributes. Metaclasses are for things __init_subclass__ cannot do: modifying the class creation process itself, changing how the class namespace is constructed before the class body runs, or creating class hierarchies that need to interoperate with type at a lower level. That is a short list.
What's an example of something only a metaclass can do?
Controlling the class namespace before the body executes. Python lets a metaclass return a custom dictionary from __prepare__ that collects the class body — so you can intercept attribute definitions as they happen, in order. __init_subclass__ only fires after the class is fully built. The enum.EnumMeta in the standard library uses exactly this to remember the order that enum members were defined and to prevent duplicate names. You cannot do that with __init_subclass__ because by the time it's called, the class is already assembled.
The other case: when you genuinely need to inject or override type-level behavior — descriptor lookup, isinstance checks, custom __call__ on the class itself. These are real use cases. They're also rare enough that most Python engineers never need to write one.
So Amir's metaclass is technically correct but probably unnecessary. He could have used __init_subclass__. I'm not going to tell him that.
Tell him. That's a legitimate code review comment — "could this be __init_subclass__? Same behavior, more readable." He might have a reason. He might not. Either way, you know enough now to have the conversation.
One thing I'm not clear on: when Amir defines a new processor in a different file, it gets registered automatically — just by importing the file that defines the class. Is that how it works?
Exactly. Class definition happens at import time in Python. The moment Python executes class StandardProcessor(BaseProcessor):, __init_subclass__ fires and the class lands in the registry. If that file is imported — even just imported, never explicitly registered — the processor is available. This is why plugin systems work: you import a module, the classes in it register themselves, and the lookup table is populated. No explicit registration call needed.
So the order routing code can do processor = BaseProcessor.REGISTRY['StandardProcessor']() and it doesn't need to know the class exists ahead of time. You add a new processor file, import it, and the routing code just works. That's why Amir built it this way.
You just described the architecture from first principles. That's not following a pattern — that's understanding why the pattern exists.
I want to make sure I haven't convinced myself I understand more than I do. What would actually break if someone wrote class BadProcessor: — no inheritance — and expected it to be in the registry?
Nothing breaks — it just doesn't register. __init_subclass__ only fires when you subclass BaseProcessor. A class that doesn't inherit from BaseProcessor never triggers it. The registry only knows about classes that declared the inheritance. This is the contract: you join the system by subclassing. The mechanism is transparent but it requires the subclass relationship.
Okay. Let me be honest about where I am: I still wouldn't write a metaclass tomorrow. But I opened Amir's ProcessorMeta this morning and it was incomprehensible. Right now I could explain it to someone. I know what mcs is. I know why __new__ gets called at class definition time, not at instance creation time. I know that __init_subclass__ is almost always the right tool instead. That's real.
That's exactly where Day 7 should land. Metaclasses are one of Python's most powerful and most misused features. Understanding them well enough to read them, evaluate whether they were the right choice, and know the modern alternative — that is the professional relationship with metaclasses. Writing one is a last resort, not a flex.
Next week we leave the object model entirely. The pipeline you've been looking at is slow not because of how objects are structured — it's slow because it runs one thing at a time. We're going to look at what it actually means for Python code to run fast: threads, processes, and async. The question isn't "what makes a class" anymore. It's "what makes a thousand orders process in half a second instead of eight."