Skip to main content

Python Metaclasses: Control How Classes Are Created

Advanced30 min5 exercises100 XP
0/5 exercises

You already know that classes create objects. But have you ever wondered — what creates the classes themselves? In Python, the answer is metaclasses.

Think of it like a factory analogy. A car factory produces cars. But who builds the car factory? A factory factory. That's exactly what a metaclass is — a class that builds other classes. It controls the rules and structure every class must follow.

Metaclasses are one of Python's most powerful features. Frameworks like Django, SQLAlchemy, and Flask use them behind the scenes to create elegant APIs. After this tutorial, you'll understand how they work and when to reach for them.

What Is type() Really Doing?

You've probably used type() to check what kind of object something is. But type() has a secret second life — it's also the default metaclass for every class in Python.

Every time you write a class statement, Python uses type() behind the scenes to create that class object. You can even create classes manually with type():

Creating classes with type()
Loading editor...

The three arguments to type() are:

  • Name — the class name as a string
  • Bases — a tuple of parent classes (empty () means inherit from object)
  • Namespace — a dictionary of attributes and methods
  • How Do You Write a Custom Metaclass?

    A custom metaclass is just a class that inherits from type. You override __new__ to control how the class object itself gets created. This runs before the class even exists.

    A metaclass that uppercases attributes
    Loading editor...

    Notice the metaclass=UppercaseMeta keyword in the class definition. This tells Python: "Don't use the default type to build this class — use UppercaseMeta instead."

    The __new__ method in a metaclass receives four arguments:

  • `mcs` — the metaclass itself (convention uses mcs instead of cls)
  • `name` — the name of the class being created
  • `bases` — the parent classes
  • `namespace` — the dictionary of everything defined in the class body
  • Can Metaclasses Enforce Rules on Classes?

    One of the most practical uses of metaclasses is validation. You can enforce that every class built with your metaclass follows certain rules — like requiring a docstring, or mandating specific methods.

    Enforcing method requirements
    Loading editor...

    Is There a Simpler Alternative to Metaclasses?

    Python 3.6 introduced __init_subclass__, a hook that runs every time a class is subclassed. It covers many use cases that previously required a full metaclass — with much simpler code.

    Auto-registering plugins with __init_subclass__
    Loading editor...

    The __init_subclass__ hook is called on the parent class whenever a new child class is created. You can even accept keyword arguments directly in the class definition — no metaclass needed.

    Metaclass approach
    class RegistryMeta(type):
        registry = {}
        def __new__(mcs, name, bases, ns):
            cls = super().__new__(mcs, name, bases, ns)
            if bases:
                RegistryMeta.registry[name] = cls
            return cls
    
    class Plugin(metaclass=RegistryMeta):
        pass
    __init_subclass__ approach
    class Plugin:
        registry = {}
        def __init_subclass__(cls, **kw):
            super().__init_subclass__(**kw)
            Plugin.registry[cls.__name__] = cls

    How Do Real Frameworks Use Metaclasses?

    The most famous metaclass example is Django's model system. When you define a model class, Django's metaclass automatically discovers your fields, creates database table mappings, and adds query methods. Here's a simplified version of that pattern:

    Django-style field discovery
    Loading editor...

    Practice Exercises

    Create a Class with type()
    Write Code

    Use the type() function (not a class statement) to create a class called Counter with:

  • An attribute count initialized to 0
  • A method increment(self) that increases count by 1 and returns the new value
  • Then create an instance, call increment() three times, and print the final count.

    Loading editor...
    Predict the Metaclass Output
    Predict Output

    What does the following code print?

    class LogMeta(type):
        def __new__(mcs, name, bases, namespace):
            print(f'Creating: {name}')
            return super().__new__(mcs, name, bases, namespace)
    
    class Base(metaclass=LogMeta):
        pass
    
    class Child(Base):
        pass
    
    print('Done')
    Loading editor...
    Build a Validation Metaclass
    Write Code

    Create a metaclass called DocstringRequired that raises a TypeError with the message "Class {name} must have a docstring" if any class using it (excluding the base class) does not have a __doc__ attribute or it is None.

    Then create:

  • A base class Documented that uses the metaclass
  • A valid subclass GoodClass with a docstring 'I have docs'
  • Print GoodClass.__doc__ at the end.

    Loading editor...
    Fix the Plugin Registry
    Fix the Bug

    The code below is supposed to auto-register plugins using __init_subclass__, but it has a bug. Fix it so the output shows both plugins registered.

    Expected output:

    pdf: PDFPlugin
    csv: CSVPlugin
    Loading editor...
    Build a Field-Discovery Metaclass
    Write Code

    Create a simplified ORM-style system:

    1. A Field class with __init__(self, field_type) that stores the type, and a __repr__ that returns 'Field(<type_name>)' (e.g. 'Field(str)')

    2. A metaclass ModelMeta that collects all Field instances into a _fields dict and also stores a _field_names list (sorted alphabetically)

    3. A Model base class using the metaclass

    4. A Product model with fields: name (str), price (float), quantity (int)

    Print Product._field_names.

    Loading editor...