Python prepare Metaclass Namespace Customization
In Python, the __prepare__ method allows developers to
customize the namespace mapping used during the execution of a class
body. This article explores how __prepare__ works, the
class creation lifecycle, why you would customize the namespace
dictionary, and how to implement it with practical code examples.
What is __prepare__?
Introduced in Python 3, __prepare__ is a special method
defined on a metaclass that returns a mapping object (such as a
dictionary or a custom dictionary subclass) before the class body is
evaluated.
By default, Python evaluates a class body using a standard
dict. When a metaclass defines __prepare__,
Python calls this method first, providing the namespace mapping into
which all class attributes, methods, and definitions are placed during
execution.
Method Signature
@classmethod
def __prepare__(metacls, name, bases, **kwds):
return dict()metacls: The metaclass itself.name: The name of the class being created.bases: A tuple of base classes the new class inherits from.**kwds: Additional keyword arguments passed in the class definition header (e.g.,class MyClass(metaclass=Meta, custom_arg=True):).- Return value: Must be a mapping object (an instance
implementing the
collections.abc.MutableMappinginterface).
The Class Creation Lifecycle
Understanding the exact sequence of class creation highlights where
__prepare__ fits:
- Metaclass Determination: Python determines the appropriate metaclass for the class.
- Namespace Preparation (
__prepare__): Python invokesmetaclass.__prepare__(name, bases, **kwds). - Class Body Execution: Python executes the class
body using the mapping returned by
__prepare__as the local namespace. - Class Instantiation (
__new__and__init__): The metaclass's__new__and__init__methods are called, receiving the populated namespace as an argument.
Without __prepare__, step 2 defaults to creating a
standard empty dictionary.
Common Use Cases
1. Disallowing Duplicate Attributes
Python natively allows overriding attributes and methods within the same class definition without error; the last definition simply overwrites previous ones. A custom namespace can catch duplicates at declaration time:
class DisallowDuplicatesDict(dict):
def __setitem__(self, key, value):
if key in self:
raise TypeError(f"Duplicate definition of '{key}' is not allowed.")
super().__setitem__(key, value)
class StrictMeta(type):
@classmethod
def __prepare__(metacls, name, bases, **kwds):
return DisallowDuplicatesDict()
def __new__(metacls, name, bases, namespace, **kwds):
return super().__new__(metacls, name, bases, dict(namespace))
class Service(metaclass=StrictMeta):
def run(self):
return "first"
# The following line raises TypeError: Duplicate definition of 'run' is not allowed.
def run(self):
return "second"2. Tracking Declaration Order and Member Metadata
While standard dictionaries preserve insertion order in modern
Python, custom namespaces can actively process attributes as they are
being defined. This pattern is foundational to the implementation of
Python's standard library enum.Enum, where member names and
assignments must be captured and managed dynamically during the class
definition phase.
Requirements and Constraints
- Mapping Interface: The returned object must
implement at least
__getitem__and__setitem__to support attribute lookup and assignment during class body execution. - Conversion in
__new__: Many C-level internals in Python require a standarddictfor the final class namespace. If__prepare__returns a custom mapping subclass, it is standard practice to pass a converteddict(namespace)tosuper().__new__.