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()

The Class Creation Lifecycle

Understanding the exact sequence of class creation highlights where __prepare__ fits:

  1. Metaclass Determination: Python determines the appropriate metaclass for the class.
  2. Namespace Preparation (__prepare__): Python invokes metaclass.__prepare__(name, bases, **kwds).
  3. Class Body Execution: Python executes the class body using the mapping returned by __prepare__ as the local namespace.
  4. 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