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