Skip to content
  • Metaclasses & Class Creation · Python

Metaclasses & Class Creation

LearnViaNotes @learnvianotes· 4 min read

Classes are objects too — created at runtime by a metaclass, which is type by default. Knowing how class creation works explains __new__, __init_subclass__, ABC and how frameworks like Django build models.

Classes are objects created by type

Python
class Dog:
    sound = "woof"

print(type(Dog), type(type))          # <class 'type'> <class 'type'>
print(isinstance(Dog, object))        # True

# The class statement is sugar for calling type(name, bases, namespace):
Cat = type("Cat", (), {"sound": "meow", "speak": lambda self: self.sound})
print(Cat().speak(), Cat.__name__)    # meow Cat

What happens when class runs

  1. Pick the metaclass

    The explicit metaclass= keyword, else the most derived metaclass of the bases, else type.

  2. Prepare the namespace

    metaclass.__prepare__(name, bases) returns the mapping the body will fill (a plain dict by default).

  3. Run the body

    The class body executes like a function body; every assignment and def lands in that namespace.

  4. Create the class

    metaclass(name, bases, namespace) → __new__ builds the class object, __init__ initialises it.

  5. Notify

    type.__new__ calls __set_name__ on each attribute (descriptors) and __init_subclass__ on the parent.

  6. Decorate

    Finally, class decorators are applied (@dataclass runs here).

__new__ vs __init__

__new__(cls, …) creates and returns the instance; __init__(self, …) initialises it. You rarely override __new__ — except for immutable types (whose value must be fixed at creation) and instance control such as singletons or caching.

Python
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, value):
        self.value = value            # NOTE: still runs on every call

a = Singleton(1)
b = Singleton(2)
print(a is b, a.value)                # True 2

class Celsius(float):                 # float is immutable → customise __new__, not __init__
    def __new__(cls, degrees):
        if degrees < -273.15:
            raise ValueError("below absolute zero")
        return super().__new__(cls, round(degrees, 1))

print(Celsius(21.456), Celsius(10) + 5)   # 21.5 15.0

__init_subclass__: the easy hook (preferred)

Most metaclass use cases are really “do something whenever a subclass is defined”. __init_subclass__ (3.6+) does exactly that with an ordinary classmethod — no metaclass needed.

Python
class Plugin:
    registry = {}

    def __init_subclass__(cls, /, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin.registry[name or cls.__name__.lower()] = cls

class CsvLoader(Plugin, name="csv"):     # class keyword arguments reach __init_subclass__
    pass

class JsonLoader(Plugin):
    pass

print(list(Plugin.registry))             # ['csv', 'jsonloader']
print(Plugin.registry["csv"] is CsvLoader)   # True

Writing a metaclass

Python
class RequireDocs(type):
    """Reject public methods without a docstring — checked when the class is defined."""
    def __new__(mcls, name, bases, ns):
        for attr, value in ns.items():
            if callable(value) and not attr.startswith("_") and not value.__doc__:
                raise TypeError(f"{name}.{attr} needs a docstring")
        return super().__new__(mcls, name, bases, ns)

class Service(metaclass=RequireDocs):
    def run(self):
        """Run the service."""

print(type(Service).__name__)            # RequireDocs
try:
    class Worker(Service):               # the metaclass is inherited by subclasses
        def stop(self):
            pass
except TypeError as e:
    print(e)                             # Worker.stop needs a docstring

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):  # runs on Config(...) — before __new__/__init__
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Config(metaclass=SingletonMeta):
    pass

print(Config() is Config())              # True
Metaclass methodRuns whenUsed for
__prepare__Before the class bodyCustom namespace (e.g. record definition order, forbid duplicates)
__new__Class object is createdInspect / modify the namespace, validate, register
__init__After __new__Post-creation setup
__call__MyClass(...) is calledControl instance creation: singletons, caching, pooling

Lighter alternatives

Python
class Box:
    def __class_getitem__(cls, item):    # powers Box[int] without a metaclass
        return f"Box of {item.__name__}"

def add_repr(cls):                        # a class decorator: receives the finished class
    cls.__repr__ = lambda self: f"<{cls.__name__} {vars(self)}>"
    return cls

@add_repr
class Point:
    def __init__(self, x):
        self.x = x

from abc import ABC
print(Box[int], Point(3))                 # Box of int <Point {'x': 3}>
print(type(ABC).__name__)                 # ABCMeta
NeedReach for
Modify or register one class after it's builtClass decorator
React to every subclass being defined__init_subclass__
Know the attribute name a field was assigned toDescriptor __set_name__
Make MyClass[...] work__class_getitem__
Change how the class namespace or MyClass() itself behavesMetaclass

Interview angle

What is a metaclass?

What is the difference between __new__ and __init__?

How would you auto-register every subclass of a base class?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report