Gyaan

Metaclasses

advanced metaclasses type meta-programming

In Python, everything is an object — including classes themselves. A metaclass is the “class of a class.” It’s what creates and configures class objects, the same way a class creates and configures instances.

In simple language, if a class is a blueprint for objects, then a metaclass is a blueprint for classes.

type() Is the Default Metaclass

Every class we write is secretly created by type. We can even see this:

class Dog:
    pass

print(type(Dog))       # <class 'type'>
print(type(Dog()))     # <class 'Dog'>

So Dog is an instance of type, and Dog() is an instance of Dog.

Creation chain
type
(metaclass)
MyMeta
(custom metaclass)
MyClass
(class)
obj
(instance)
type(obj) = MyClass | type(MyClass) = MyMeta | type(MyMeta) = type

Creating Classes Dynamically with type()

We can create classes on the fly using type(name, bases, dict):

# These two are equivalent
class Dog:
    sound = "woof"

Dog = type("Dog", (), {"sound": "woof"})

This is what Python does under the hood every time we write a class statement.

Writing a Custom Metaclass

A custom metaclass inherits from type and overrides __new__ or __init__ to customize class creation.

class ValidatedMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Require all classes to have a 'version' attribute
        if "version" not in namespace:
            raise TypeError(f"{name} must define a 'version' attribute")
        return super().__new__(mcs, name, bases, namespace)

class MyPlugin(metaclass=ValidatedMeta):
    version = "1.0"  # this works

class BadPlugin(metaclass=ValidatedMeta):
    pass  # TypeError: BadPlugin must define a 'version' attribute

When Python sees metaclass=ValidatedMeta, it calls ValidatedMeta.__new__() instead of type.__new__() to create the class.

Practical Uses

Metaclasses are used in some well-known libraries:

  • Django ORMModel classes use metaclasses to turn field definitions into database schema
  • Abstract Base ClassesABCMeta enforces that subclasses implement required methods
  • Plugin registration — auto-register every subclass in a registry

Here’s a registration example:

class PluginMeta(type):
    registry = {}
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:  # don't register the base class itself
            mcs.registry[name] = cls
        return cls

class Plugin(metaclass=PluginMeta):
    pass

class AuthPlugin(Plugin):
    pass

class CachePlugin(Plugin):
    pass

print(PluginMeta.registry)
# {'AuthPlugin': <class 'AuthPlugin'>, 'CachePlugin': <class 'CachePlugin'>}

The Simpler Alternative: init_subclass

Since Python 3.6, we have __init_subclass__() which handles most use cases without needing a metaclass.

class Plugin:
    registry = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin.registry[cls.__name__] = cls

class AuthPlugin(Plugin):
    pass

print(Plugin.registry)  # {'AuthPlugin': <class 'AuthPlugin'>}

Much simpler. Same result.

When NOT to Use Metaclasses

Metaclasses are powerful but rarely needed. Before reaching for one, consider:

  • __init_subclass__ — for subclass hooks (Python 3.6+)
  • Class decorators — for modifying a class after creation
  • Descriptors — for custom attribute behavior

The famous quote: “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t.” — Tim Peters

In simple language, metaclasses let us control how classes are built. They’re the factory that produces factories. Incredibly powerful, but for most of us, __init_subclass__ and class decorators will do the job.