Skip to content

Metaclasses

A metaclass is “the class of a class.” Just as an object is created by calling its class, a class is created by calling its class — the metaclass. By default every class’s metaclass is type; writing a custom metaclass lets you hook into and modify class creation itself (adding methods, validating structure, registering subclasses) before any instance ever exists.

Without a metaclass, enforcing a rule across every class in a family (e.g. “every subclass must define a name attribute”) has to be checked manually, or not at all — nothing stops a subclass from forgetting it until it fails at runtime:

class Plugin:
def __init__(self):
if not hasattr(self, "name"):
raise TypeError("Plugin subclasses must define 'name'")
class Broken(Plugin):
pass # no error until you actually instantiate it
# Broken() # fails here, not at class-definition time
class PluginMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases and "name" not in namespace: # skip the base class itself
raise TypeError(f"{name} must define 'name'")
return cls
class Plugin(metaclass=PluginMeta):
pass
class Broken(Plugin):
pass # TypeError raised immediately, at class-definition time
# type(x) tells you an object's class; type(SomeClass) tells you its metaclass
class Point:
pass
print(type(Point())) # <class '__main__.Point'>
print(type(Point)) # <class 'type'> -- every class's default metaclass
  • Enforced at definition time: broken subclasses fail as soon as the class is defined, not when someone eventually instantiates it.
  • Framework-level hooks: Django’s ORM (models.Model), SQLAlchemy’s declarative base, and abc.ABCMeta all use metaclasses to auto-register fields, wire up table mappings, or enforce abstract methods — behavior that has to happen once per class, not per instance.
  • Centralizes cross-cutting class logic: instead of repeating a check in every __init__, it lives in one place that runs for every subclass automatically.
  • class Foo(metaclass=Meta): is what triggers Meta.__new__/Meta.__init__ instead of the default type.__new__.
  • Metaclasses are rare in application code — reach for a class decorator or __init_subclass__ first; both solve most “do something when a class is defined” problems with far less complexity.
  • __init_subclass__ (Python 3.6+) covers the most common metaclass use case (validating/registering subclasses) without writing a metaclass at all:
class Plugin:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if "name" not in cls.__dict__:
raise TypeError(f"{cls.__name__} must define 'name'")
class Broken(Plugin):
pass # TypeError, no metaclass needed
  • A common interview gotcha: “everything in Python is an object” extends to classes themselves — a class is an instance of its metaclass, which is itself an instance of type (and type is an instance of itself).
  1. What is type(SomeClass) for an ordinary class with no custom metaclass?

    Answer`type` — every class's default metaclass, unless one is explicitly specified.
  2. What’s the simplest built-in alternative to a custom metaclass for validating subclasses?

    Answer`__init_subclass__`, defined on the base class — it runs automatically whenever a subclass is created, without needing a separate metaclass.
  3. Why do ORMs like Django’s models commonly use metaclasses?

    AnswerTo process class-level field declarations (e.g. `name = CharField()`) into database schema/mapping information once, at class-definition time, rather than repeating that work on every instance.