9: Meeting Metaclasses

Summary

  • A class is an object that creates instances

  • A metaclass is an object that creates classes

  • The base metaclass is type

  • type can also be used as a function to dynamically create classes

  • A metaclass has to inherit from type

  • A metaclass goes through the usual stages of calling __new_- to create an instance of the class and __init__ to initialize it

  • A metaclass also has __prepare__ which can supply a data structure suitable for storing the namespace

  • Metaclasses can be used for many things but they are mostly sophisticated
    • Typical uses are implementing a final class and a singleton

  • The metaclass __call__ is used to convert a class object into a callable
    • You can override this to control how the class contructs an instance

  • As a class is a callable it can be used as a decorator with its behavior set by its metaclass’s __call__

  • As a class is a callable it can also be decorated by either a class or a function

  • There are many cases where the job of a metaclass can be taken on by a decorator

What to use Metaclasses For

  • Adding attributes or methods automatically

  • Enforcing certain rules

  • Complex projects, frameworks and libraries

When to use Metaclasses

  • Most day to day programming you do not need metaclasses

  • They are an advanced tool
    • Django often uses metaclasses

Program

"""
Metaclass Demonstration Program

This program demonstrates:
1. What a metaclass is
2. How Python uses the base metaclass 'type'
3. Creating a custom metaclass
4. Using __prepare__, __new__, __init__, and __call__
5. Automatically adding attributes
6. Enforcing rules on classes
7. Implementing a Singleton using a metaclass
8. Dynamically creating a class using type()
"""

# ============================================================
# 1. Demonstrating that classes are objects created by 'type'
# ============================================================

class Example:
    pass

# Every class in Python is actually an instance of the metaclass 'type'
print("Metaclass of Example:", type(Example))


# ============================================================
# 2. Dynamically creating a class using type()
# ============================================================

# type(name, bases, dictionary_of_attributes)

DynamicClass = type(
    "DynamicClass",           # class name
    (),                       # base classes
    {"message": "Hello from a dynamically created class!"}
)

# Create an instance of the dynamically created class
dynamic_instance = DynamicClass()
print(dynamic_instance.message)


# ============================================================
# 3. Creating a Custom Metaclass
# ============================================================

class MyMeta(type):
    """
    Custom metaclass.
    Metaclasses must inherit from 'type'.
    """

    @classmethod
    def __prepare__(metacls, name, bases):
        """
        Called before the class body executes.
        Returns the object used to store the class namespace.
        """
        print(f"\n__prepare__ called for class: {name}")
        return {}

    def __new__(metacls, name, bases, namespace):
        """
        Creates the class object.
        Similar to __new__ in normal classes, but this creates the class itself.
        """

        print(f"__new__ called for class: {name}")

        # Example use: automatically add an attribute to the class
        namespace["added_by_metaclass"] = "This attribute was added automatically"

        # Example rule enforcement:
        # Require the class to define a method named 'run'
        if "run" not in namespace:
            raise TypeError(f"Class '{name}' must define a 'run' method")

        return super().__new__(metacls, name, bases, namespace)

    def __init__(cls, name, bases, namespace):
        """
        Initializes the class after it has been created.
        """
        print(f"__init__ called for class: {name}")
        super().__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        """
        Controls how instances of the class are created.

        Normally:
        class() -> __new__ -> __init__

        Here we intercept that process.
        """
        print(f"__call__ invoked to create instance of {cls.__name__}")
        instance = super().__call__(*args, **kwargs)
        return instance


# ============================================================
# 4. Creating a Class that Uses the Metaclass
# ============================================================

class Worker(metaclass=MyMeta):
    """
    This class uses MyMeta as its metaclass.
    """

    def __init__(self, name):
        self.name = name

    def run(self):
        print(f"{self.name} is running!")


# Create an instance
worker = Worker("Alice")
worker.run()

# Access attribute added automatically by the metaclass
print(worker.added_by_metaclass)


# ============================================================
# 5. Implementing a Singleton Using a Metaclass
# ============================================================

class SingletonMeta(type):
    """
    Metaclass implementing the Singleton pattern.

    Only one instance of the class can exist.
    """

    _instances = {}

    def __call__(cls, *args, **kwargs):
        """
        Controls instance creation.
        If instance already exists, return it instead of creating a new one.
        """

        if cls not in cls._instances:
            print("Creating new Singleton instance")
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        else:
            print("Returning existing Singleton instance")

        return cls._instances[cls]


class DatabaseConnection(metaclass=SingletonMeta):
    """
    Example class using the Singleton metaclass.
    """

    def __init__(self):
        print("Initializing database connection")


# Create multiple objects
db1 = DatabaseConnection()
db2 = DatabaseConnection()

# Verify they are the same object
print("db1 is db2:", db1 is db2)

Program Output

Metaclass of Example: <class 'type'>
Hello from a dynamically created class!

__prepare__ called for class: Worker
__new__ called for class: Worker
__init__ called for class: Worker
__call__ invoked to create instance of Worker
Alice is running!
This attribute was added automatically
Creating new Singleton instance
Initializing database connection
Returning existing Singleton instance
db1 is db2: True