32.2 How Python Creates a Class: __prepare__, __new__, __init__
At the heart of Python’s class creation mechanism lies the type constructor. The familiar class keyword is syntactic sugar for a call to type(name, bases, dict). However, when a metaclass is involved, this process becomes a meticulously orchestrated three-step dance involving the special methods __prepare__, __new__, and __init__. Understanding this sequence is paramount to mastering advanced metaprogramming in Python.
The Role of __prepare__
The process begins with __prepare__, a class method (though declared with @classmethod only when necessary on a regular class) that is called even before the class body is executed. Its sole purpose is to create and return the namespace object that will be used to collect all the attributes (methods, class variables, etc.) defined within the class body.
Why it exists: Before the introduction of __prepare__ in PEP 3115, the class body was executed in a standard dictionary. This made ordered execution impossible because dictionaries did not preserve insertion order. __prepare__ allows a metaclass to provide an alternative mapping type, such as an OrderedDict, to capture the definition order. This is crucial for frameworks like ORMs or serialization libraries that care about the order in which fields are declared.
from collections import OrderedDict
class OrderedMeta(type):
@classmethod
def __prepare__(cls, name, bases, **kwargs):
# Print to show when this is called
print(f"__prepare__({cls}, {name}, {bases}, {kwargs})")
# Return an OrderedDict instead of a regular dict
return OrderedDict()
def __new__(cls, name, bases, namespace, **kwargs):
print(f"__new__({cls}, {name}, {bases}, {namespace}, {kwargs})")
return super().__new__(cls, name, bases, dict(namespace))
def __init__(self, name, bases, namespace, **kwargs):
print(f"__init__({self}, {name}, {bases}, {namespace}, {kwargs})")
super().__init__(name, bases, namespace)
class MyClass(metaclass=OrderedMeta):
attribute_z = "z"
attribute_a = "a"
def method(self): pass
# The namespace used for MyClass is an OrderedDict, preserving order.
print("Namespace order:", list(MyClass.__dict__.keys()))
Output:
__prepare__(<class '__main__.OrderedMeta'>, MyClass, (), {})
__new__(<class '__main__.OrderedMeta'>, MyClass, (), OrderedDict([('__module__', '__main__'), ('__qualname__', 'MyClass'), ('attribute_z', 'z'), ('attribute_a', 'a'), ('method', <function MyClass.method at 0x...>)]), {})
__init__(<class '__main__.MyClass'>, MyClass, (), {'__module__': '__main__', '__qualname__': 'MyClass', 'attribute_z': 'z', 'attribute_a': 'a', 'method': <function MyClass.method at 0x...>}, {})
Namespace order: ['__module__', '__qualname__', 'attribute_z', 'attribute_a', 'method', '__dict__', ...]
The Power of __new__
Next, the __new__ method of the metaclass is called. This is where the class object is actually created. It receives the metaclass itself (cls), the name of the future class (name), its base classes (bases), the populated namespace (namespace), and any keyword arguments passed in the class definition.
Why it’s powerful: __new__ has the ultimate authority. It can modify the namespace, change the base classes, or even decide not to create the class at all by returning a different object. It is the primary method for altering the fundamental construction of the class. The method must return the newly created class object.
class InspectingMeta(type):
def __new__(cls, name, bases, namespace, **kwargs):
print("Namespace before creation:", namespace)
# Example: Convert all non-dunder attribute names to uppercase
new_namespace = {}
for attr_name, attr_value in namespace.items():
if not attr_name.startswith('__'):
new_namespace[attr_name.upper()] = attr_value
else:
new_namespace[attr_name] = attr_value
# Always call the superclass __new__ to create the actual object
return super().__new__(cls, name, bases, new_namespace)
class Example(metaclass=InspectingMeta):
secret_value = 42
def a_method(self): pass
# The class was created with modified attribute names
print(hasattr(Example, 'secret_value')) # Output: False
print(hasattr(Example, 'SECRET_VALUE')) # Output: True
print(Example.SECRET_VALUE) # Output: 42
Finalizing with __init__
Finally, the __init__ method of the metaclass is called. It receives the newly created class object (self) as its first argument, along with the same parameters as __new__. The class already exists at this point.
Key distinction from __new__: Use __init__ to perform finalization and setup on the already-created class. You can register the class elsewhere, add additional attributes, or perform validation checks. You cannot change the fundamental identity of the class (like its name or bases) here; that must be done in __new__. This method does not return anything.
class RegistryMeta(type):
# A registry to hold all classes created with this metaclass
registry = {}
def __init__(cls, name, bases, namespace, **kwargs):
super().__init__(name, bases, namespace)
# 'cls' is the newly created class. We can now operate on it.
# Register the class by its name
RegistryMeta.registry[name] = cls
# Add a simple attribute to the class as an example
cls.registered_from_meta = True
class Base(metaclass=RegistryMeta): pass
class ModelA(Base): pass
class ModelB(Base): pass
print(RegistryMeta.registry)
# Output: {'Base': <class '__main__.Base'>, 'ModelA': <class '__main__.ModelA'>, 'ModelB': <class '__main__.ModelB'>}
print(ModelA.registered_from_meta) # Output: True
Common Pitfalls and Best Practices
- Inheritance Chain: Always call the superclass method in
__new__and__init__usingsuper(). Failing to do so can break the chain of class creation, especially when using multiple metaclasses, which is complex and often best avoided. - Immutability in
__init__: Remember, the class is already built. You cannot change its name or bases in__init__. Such modifications are only possible in__new__. - Keyword Arguments: Both
__new__and__init__should accept**kwargsto gracefully handle any extra keyword arguments passed in the class definition (e.g.,class MyClass(metaclass=Meta, tag="special")). These kwargs are also passed to__prepare__. - Object in
__new__: The__new__method must return an object. Typically, this is the object created bytype.__new__. Forgetting to return this object is a common and critical error. - Performance: Metaclasses add overhead to class creation. Since classes are typically created once at import time, this is rarely an issue, but it’s something to be aware of in highly dynamic code. The three-step process is why defining a class with a metaclass is slower than defining a regular class.