Metaclasses: Classes That Make Classes
1 · The lesson
readA metaclass is the class of a class. If Account() creates an Account instance, then type(...) — or some subclass of type — creates Account itself. Once that idea clicks, the rest of this lesson is mechanical.
This is the deepest plumbing in the Python object model, and it's where most "magical" frameworks (Django ORM, SQLAlchemy declarative, Pydantic v1, abc.ABCMeta) hide their wiring. You'll almost never write a metaclass in application code — __init_subclass__ and class decorators cover 95% of the cases people reach for metaclasses to solve. But understanding them turns those frameworks from magic into "oh, that's just a __new__ override."
1. Classes Are Objects Too
Every value in Python has a type. Integers are instances of int. Strings are instances of str. And classes — the things you write with class Foo: — are instances of type.
class Foo: pass print(type(42)) # <class 'int'> print(type("hi")) # <class 'str'> print(type(Foo)) # <class 'type'> — Foo itself is a type instance print(type(Foo())) # <class '__main__.Foo'>
type is the default metaclass. When Python executes class Foo: ..., it calls type(...) under the hood, which produces a class object and binds it to the name Foo. The class object is a runtime value — you can pass it around, store it in a dict, attach attributes to it, return it from a function.
Foo.label = "registered" # classes have a __dict__, just like instances print(Foo.label) # registered classes = [int, str, Foo] # store classes in containers for cls in classes: print(cls.__name__) # int / str / Foo
setup added so this can run · defines Foo
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) Foo = _AutoMock('Foo')
2. type(name, bases, namespace) — The Three-Argument Form
type has two completely different uses depending on argument count:
type(obj)— one argument — returns the type ofobj. The familiar form.type(name, bases, namespace)— three arguments — creates a new class. This is whatclasssyntax compiles to.
# Build a class entirely without `class` syntax def greet(self): return f"Hi, I'm {self.name}." Person = type( "Person", # __name__ (object,), # base classes — tuple, even for one { # namespace dict — methods and attributes "species": "Homo sapiens", "__init__": lambda self, name: setattr(self, "name", name), "greet": greet, }, ) p = Person("Hedy") print(p.greet()) # Hi, I'm Hedy. print(p.species) # Homo sapiens print(type(p)) # <class '__main__.Person'> print(type(Person)) # <class 'type'>
That Person is indistinguishable from one built with class Person:. Python builds the namespace dict by executing the class body and then calls type(name, bases, namespace). The class keyword is sugar for exactly this call.
3. class Foo: ... Is Sugar — Demonstrated
To make the equivalence concrete:
class Account: rate = 0.05 def __init__(self, balance): self.balance = balance def interest(self): return self.balance * Account.rate # is roughly equivalent to: def __init__(self, balance): self.balance = balance def interest(self): return self.balance * Account.rate Account = type("Account", (object,), { "rate": 0.05, "__init__": __init__, "interest": interest, }) a = Account(1000) print(a.interest()) # 50.0
Same object, same behaviour. The class block is a more readable way to spell it. Knowing the desugared form is the key — when you write a metaclass, you're customising what gets called instead of type(...).
4. Defining a Metaclass
A metaclass is just a class that inherits from type. Specify it on a class with metaclass=:
class Meta(type): def __new__(mcs, name, bases, namespace): print(f"Meta.__new__ creating class {name!r}") print(f" bases = {bases}") print(f" namespace keys = {list(namespace)}") cls = super().__new__(mcs, name, bases, namespace) return cls def __init__(cls, name, bases, namespace): print(f"Meta.__init__ initialising class {name!r}") super().__init__(name, bases, namespace) class Foo(metaclass=Meta): x = 1 def hello(self): return "hi" # Output appears IMMEDIATELY at class-definition time: # Meta.__new__ creating class 'Foo' # bases = (<class 'object'>,) # namespace keys = ['__module__', '__qualname__', 'x', 'hello'] # Meta.__init__ initialising class 'Foo' f = Foo() # creating an *instance* doesn't trigger Meta again print(f.hello()) # hi
The arguments to Meta.__new__ are exactly the arguments type itself receives:
mcs— the metaclass (Meta). By conventionmcs(orcls), notself.name— the name of the class being created ("Foo").bases— tuple of base classes.namespace— the dict produced by executing the class body.
Subclasses of a class with metaclass=Meta inherit the metaclass automatically — you set it once at the top of the hierarchy.
5. Plugin Registry — A Real-ish Metaclass
The canonical metaclass example: every subclass of Plugin should auto-register itself in a central dict, so the framework can discover all plugins without an explicit list.
class Registry(type): plugins = {} def __new__(mcs, name, bases, namespace): cls = super().__new__(mcs, name, bases, namespace) if bases: # skip the abstract base itself Registry.plugins[name] = cls return cls class Plugin(metaclass=Registry): """Base for all plugins — never instantiated directly.""" def run(self): raise NotImplementedError class JSONExporter(Plugin): def run(self): return "exported as JSON" class CSVExporter(Plugin): def run(self): return "exported as CSV" print(Registry.plugins) # {'JSONExporter': <class '__main__.JSONExporter'>, # 'CSVExporter': <class '__main__.CSVExporter'>} for name, cls in Registry.plugins.items(): print(name, "->", cls().run())
No register() calls anywhere. Importing the file is enough — the metaclass runs at class-creation time and grabs every subclass automatically. That's why frameworks reach for this pattern: zero ceremony at the use site.
6. Enforcing Conventions — TypeError at Creation Time
A metaclass sees every method name before an instance exists. Reject anything that doesn't match your convention:
import re SNAKE_CASE = re.compile(r"^_?_?[a-z][a-z0-9_]*_?_?$") class SnakeCaseEnforcer(type): def __new__(mcs, name, bases, namespace): for attr_name, value in namespace.items(): if callable(value) and not SNAKE_CASE.fullmatch(attr_name): raise TypeError( f"{name}.{attr_name} violates snake_case convention" ) return super().__new__(mcs, name, bases, namespace) class Good(metaclass=SnakeCaseEnforcer): def do_thing(self): pass # fine def __init__(self): pass # fine — dunders match the regex # class Bad(metaclass=SnakeCaseEnforcer): # def DoThing(self): pass # TypeError: Bad.DoThing violates snake_case convention
The error fires at class definition, not when anyone calls DoThing(). Failing fast at import time is the entire reason to use a metaclass for validation — by the time tests run, the module won't even load.
7. __init_subclass__ — The Modern Alternative
Python 3.6 added a hook that handles 95% of the "do something when a subclass is created" use case without a metaclass. Override __init_subclass__ on the parent class:
class Plugin: plugins = {} def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) Plugin.plugins[cls.__name__] = cls def run(self): raise NotImplementedError class JSONExporter(Plugin): def run(self): return "json" class CSVExporter(Plugin): def run(self): return "csv" print(Plugin.plugins) # {'JSONExporter': <...>, 'CSVExporter': <...>}
setup added so this can run · defines kwargs
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) kwargs = _AutoMock('kwargs')
Same behaviour as the metaclass version, no type subclass in sight. __init_subclass__ is a classmethod on the parent that fires every time a subclass is created. You can even pass keyword arguments through the class line:
class Plugin: def __init_subclass__(cls, *, category, **kwargs): super().__init_subclass__(**kwargs) print(f"registering {cls.__name__} in category {category}") class JSONExporter(Plugin, category="export"): pass # registering JSONExporter in category export
setup added so this can run · defines kwargs
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) kwargs = _AutoMock('kwargs')
Reach for __init_subclass__ first. Only fall back to a metaclass when you need to customise the namespace dict, intercept attribute lookup on the class itself, or do something __init_subclass__ genuinely can't.
8. __class_getitem__ — Briefly
List[int], Dict[str, int], MyContainer[Foo] all call __class_getitem__ on the class:
class Box: def __class_getitem__(cls, item): return f"Box of {item.__name__}" print(Box[int]) # Box of int
The stdlib uses this for the generics in typing and (since 3.9) the built-in containers like list[int]. You'll almost never write one yourself, but knowing the hook exists demystifies what List[int] actually is — a regular method call dressed up in square brackets.
9. The Honest Rule
If a class decorator or __init_subclass__ can do the job, do not write a metaclass. Metaclasses fight inheritance: the moment a class needs to combine two libraries that both use metaclasses, you get:
TypeError: metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its basesThere is no clean fix — you have to manually create a new metaclass that subclasses both, which only works if neither library defends against this. Real bug. Real pain.
Use this priority order:
| Need | Reach for |
|---|---|
| Add behaviour to a single class | A regular decorator |
| React to every subclass being created | __init_subclass__ |
| Transform/validate attributes on a class | Class decorator |
| Customise the namespace dict during creation | __prepare__ on a metaclass |
Need to override __call__ on the class itself | Metaclass |
| Combining two metaclassed frameworks | Don't — find another approach |
10. Where You'll Actually Meet Metaclasses
You won't write them often. You'll use them constantly:
- Django ORM —
class User(models.Model):usesModelBase(a metaclass) to translate field declarations into the SQL schema, register the model in the app registry, and build the Manager. - SQLAlchemy declarative —
DeclarativeMetawires column descriptors into a mapper. - Pydantic v1 —
ModelMetaclassbuilds the validation/serialisation machinery from field annotations. (v2 moved most of this into Rust extension code, but the metaclass shell is still there.) abc.ABCMeta— the metaclass behindabc.ABC. Tracks abstract methods, blocks instantiation of subclasses that haven't implemented them all.enum.EnumMeta— turnsclass Color(Enum): RED = 1into a singleton-per-value enum.
If you find yourself "reading the source of a framework to figure out why my class behaves strangely," look for a metaclass at the top of the hierarchy. That's almost always where the magic lives.
Common Mistakes
1. Writing a metaclass when __init_subclass__ would do. See Section 7 — the modern hook is almost always sufficient and doesn't poison the inheritance tree with metaclass conflicts.
2. Metaclass conflicts when subclassing across frameworks
class MyModel(SQLAlchemyBase, DjangoModel): # likely TypeError ...
setup added so this can run · defines SQLAlchemyBase, DjangoModel
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) SQLAlchemyBase = _AutoMock('SQLAlchemyBase') DjangoModel = _AutoMock('DjangoModel')
Two libraries, two metaclasses, no common ancestor. There's no clean fix — and this is the single biggest reason "avoid metaclasses if you can" is industry advice.
3. Treating metaclass methods as instance methods
class Meta(type): def show(cls): print(f"I am {cls.__name__}") class Foo(metaclass=Meta): pass Foo.show() # I am Foo — works (Foo is an instance of Meta) Foo().show() # AttributeError — instances are not instances of Meta
Methods defined on the metaclass are callable on the class (because the class is an instance of the metaclass), not on instances of the class. This is the symmetrical mind-bender that costs everyone half an hour the first time.
4. Expecting Meta.__init__ to run on instances of the class. It does not. Meta.__new__/Meta.__init__ run once at class-creation time. The class's own __init__ runs on every instance. They are completely different lifecycles.
5. Forgetting super().__new__(mcs, name, bases, namespace) — return a class object built by type.__new__, not an arbitrary value. Skipping the super() call breaks the type system in subtle ways (isinstance checks misbehave, MRO computation fails).
🎯 Your Turn — Build a Singleton
A singleton is a class that always returns the same instance — Config() and Config() should be the same object. The canonical metaclass for this overrides __call__ on the metaclass so calling Config(...) is intercepted before __init__ runs.
Build it two ways:
1. As a Singleton(type) metaclass.
2. As a simple __new__ override on a base class.
Compare them, then decide which one you'd actually ship.
# Metaclass version class Singleton(type): # TODO 1: keep a dict mapping cls -> instance # TODO 2: override __call__(cls, *args, **kwargs) # - if cls already has an instance, return it # - otherwise call super().__call__(...) and store the result ... class Config(metaclass=Singleton): def __init__(self, name="default"): self.name = name a = Config("first") b = Config("second") print(a is b) # True print(a.name, b.name) # first first — __init__ ran only once
Hint 1 — Singleton.__call__ runs when you call the class
The metaclass's __call__ is what fires when someone writes Config(...). Inside it, cls is Config. Store instances on the metaclass: cls._instances = {} on the metaclass itself, keyed by cls.
Hint 2 — Only call super().__call__ once
The first time a class is requested, you need super().__call__(*args, **kwargs) to actually construct it (this runs __init__). Cache the result. On subsequent calls, return the cached instance without calling super — that's what skips re-running __init__.
Show full solution
# ----- Version A: metaclass ----- class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in Singleton._instances: Singleton._instances[cls] = super().__call__(*args, **kwargs) return Singleton._instances[cls] class Config(metaclass=Singleton): def __init__(self, name="default"): self.name = name a = Config("first") b = Config("second") print(a is b) # True print(a.name) # first — second call skipped __init__ # ----- Version B: __new__ override ----- class SingletonBase: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance class ConfigB(SingletonBase): def __init__(self, name="default"): self.name = name a = ConfigB("first") b = ConfigB("second") print(a is b) # True print(a.name) # second — __init__ STILL runs on every call!
setup added so this can run · defines args, kwargs
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) args = _AutoMock('args') kwargs = _AutoMock('kwargs')
Two important differences:
- The metaclass version intercepts the call before
__init__.__init__runs exactly once — the first time. SubsequentConfig("anything")calls return the cached instance untouched. - The
__new__version returns the cached instance, but Python still calls__init__on it every time.b.nameends up"second"because the second call re-initialises the same object. Often this is a bug.
There's also the "simplest of all" approach — just use a module. A module is a singleton by language design:
# config.py name = "default" def set_name(n): global name name = n # everywhere else import config config.name # one shared "instance", no class gymnastics needed
Recommendation: prefer the module-as-singleton approach in real code. If you genuinely need a class (because you need polymorphism, or you're matching an existing API), use the metaclass version — it has the correct semantics around __init__. Reach for the __new__ trick only when you can guarantee __init__ is idempotent.
What You Learned
- Classes are objects, and their type is
type(or a subclass oftype— a metaclass). type(name, bases, namespace)is the three-argument form that creates a class.class Foo:is sugar for exactly that call.- A metaclass customises class creation by overriding
type.__new__,type.__init__, ortype.__call__. The arguments mirrortype's: name, bases, namespace dict. - The metaclass runs once at class-definition time, not per instance. Frequent confusion:
Meta.__init__is not the class's__init__. __init_subclass__is the modern Python 3.6+ hook that replaces metaclasses for 95% of "react to subclasses" cases. Always try it first.- Class decorators cover most of the rest. Metaclasses fight inheritance — combining libraries that each use one is genuinely painful.
- Real frameworks using metaclasses: Django ORM, SQLAlchemy declarative, Pydantic v1,
abc.ABCMeta,enum.EnumMeta. You'll use these constantly; you'll write your own rarely.
Next: Descriptors & Properties — the protocol behind @property, @classmethod, methods themselves, and a hundred ORM field types.