Back to Blog
Python

Python Type Metaclass: How Classes Are Created

python type metaclass: Explains how Python's type metaclass creates classes, how to subclass type for custom metaclasses, and when lighter alternatives like __init_sub...

pythonmetaclassestypeclass creationpython internalsoop
Diagram showing a class object being created by the type metaclass, with a custom metaclass subclassing type in the background.

Every class in Python is an instance of a metaclass, and for the vast majority of classes that metaclass is type. When you write a class statement, Python calls type (or a subclass of type) to build the class object. Understanding how python type metaclass works means understanding what type does with the class name, bases, and namespace, and how you can hook into that process.

The Two Roles of type

type serves two distinct purposes in Python. With one argument, type(obj) returns the type of an object:

class Product: pass print(type(Product)) # <class 'type'> print(type(42)) # <class 'int'>

With three arguments, type(name, bases, namespace) creates a new class. This three-argument form is the lower-level equivalent of the class statement. The class statement is syntactic sugar over this call.

Creating a Class Directly with type

The following two definitions produce functionally identical classes:

# Class statement class Product: category = "general" # Direct type call Product = type("Product", (), {"category": "general"})

The type call receives the class name as a string, a tuple of base classes, and a dictionary representing the class namespace. Methods defined in the class body appear as callable values in that dictionary.

This direct form is rarely needed in application code, but it clarifies what the interpreter does behind the scenes. A class statement passes the same three pieces of information to the metaclass.

Subclassing type to Build a Custom Metaclass

A custom metaclass is a subclass of type. The most common customization point is __new__, which runs before the class object exists:

class ValidatedMeta(type): def __new__(mcls, name, bases, namespace): if "price" not in namespace: raise TypeError(f"{name} must define a price attribute") return super().__new__(mcls, name, bases, namespace)

Use it with the metaclass keyword:

class Product(metaclass=ValidatedMeta): price = 10

The first argument to __new__ is the metaclass itself (mcls), not the class being created. The class does not exist yet, so self is not available. If the validation fails, Python raises TypeError at class definition time, before any instance is created.

The Class Creation Sequence

When Python encounters a class statement, it performs the following steps:

  1. It determines the metaclass from the metaclass= keyword argument or from the most derived metaclass among the base classes.
  2. It builds the namespace by executing the class body.
  3. It calls the metaclass with (name, bases, namespace).
  4. The metaclass's __new__ creates and returns the class object.
  5. The metaclass's __init__ receives the newly created class for optional initialization.

The metaclass's __call__ method is a separate hook. It controls what happens when the class is instantiated. A metaclass can use __call__ to intercept Product() calls, which is how frameworks implement singleton behavior or instance caching.

Practical Use Cases for Metaclasses

The most common uses of metaclasses fall into two categories: validation and registration.

Validation keeps invariants in one place. The ValidatedMeta example above ensures every class in a hierarchy defines price, which prevents a whole class of runtime attribute errors.

Registration collects subclasses automatically. A metaclass can append each newly created class to a registry:

class RegistryMeta(type): registry = [] def __new__(mcls, name, bases, namespace): cls = super().__new__(mcls, name, bases, namespace) if name != "Base": mcls.registry.append(cls) return cls class Base(metaclass=RegistryMeta): pass class FirstPlugin(Base): pass class SecondPlugin(Base): pass print(RegistryMeta.registry) # [<class 'FirstPlugin'>, <class 'SecondPlugin'>]

Frameworks such as Django and SQLAlchemy use metaclasses to transform declarative class definitions into database models, but application code rarely needs that level of machinery.

Runtime Costs and Debugging Tradeoffs

Metaclass methods execute once per class definition, not once per instance. The performance cost is therefore negligible for typical applications. The real cost is cognitive. A metaclass adds a layer of indirection that is invisible when reading the class body. The behavior of Product is no longer fully described by its own code; part of it lives in ValidatedMeta.

This indirection also complicates debugging. Tracebacks show frames inside metaclass __new__ and __init__, and a failure at class definition time can be surprising if you are used to errors appearing at instantiation. Keep metaclass logic small and focused, and document the contract it enforces.

init_subclass as a Lighter Alternative

Python 3.6 introduced __init_subclass__, which runs when a subclass is created. For many validation and registration cases, it is simpler than a metaclass because it lives in the base class:

class Base: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) if not hasattr(cls, "price"): raise TypeError("price is required") class Product(Base): price = 10

The cls argument is the newly created subclass. Unlike a metaclass, __init_subclass__ does not intercept the base class itself, and it cannot modify the namespace before the class is built. If you only need to react to a subclass after it exists, __init_subclass__ is the right tool.

ConcernMetaclass__init_subclass__
Runs for base classYesNo
Can modify namespaceYesNo
Hook locationSubclass of typeMethod on base class
Typical useFramework-level transformationValidation and registration

Metaclass Conflicts in Inheritance

When a class inherits from multiple bases that use different metaclasses, Python raises TypeError: metaclass conflict. The metaclass of a derived class must be a subclass of every metaclass in its bases. This is a practical concern when combining classes from different libraries that each install a metaclass.

The resolution is to define a combined metaclass that inherits from both:

class CombinedMeta(MetaA, MetaB): pass

If MetaA and MetaB have incompatible __new__ implementations, the conflict cannot be resolved cleanly, and you must restructure the class hierarchy. This is one reason to prefer __init_subclass__ when you control the base class: it avoids introducing a second metaclass into the inheritance chain.

python type metaclass: How Classes Are Created | RYUSLOG DEV