Skip to main content

7.9 - Metaclasses

Metaclasses are an advanced feature of Python, often shrouded in mystery. They are a fundamental part of the language’s OOP (Object-Oriented Programming) model and provide a deeper understanding of how classes and objects work in Python.


7.9.1 - The Essence of Metaclasses

7.9.1.1 - What Are Metaclasses?

Metaclasses are, succinctly put, the classes of classes. In Python, classes themselves are objects. Just as an object is an instance of a class, a class is an instance of a metaclass. While most Python programming involves object-level classes, metaclasses operate at the class-level, controlling how classes are defined and behave.

7.9.1.2 - The Role of type

In Python, type is the built-in metaclass that Python uses by default. It's what gets called when you define a class with the class keyword.

class MyClass:
pass

# MyClass is an instance of 'type'.
print(isinstance(MyClass, type)) # Output: True

type can also dynamically create classes. It takes the name of the class, its base classes, and a dictionary containing attributes and methods.

MyDynamicClass = type('MyDynamicClass', (object,), {'x': 5})

7.9.2 - Defining Custom Metaclasses

7.9.2.1 - Subclassing type

Custom metaclasses are created by subclassing type. In doing so, you can modify or extend the behavior of class creation and initialization.

class MyMeta(type):
pass

class MyClass(metaclass=MyMeta):
pass

# MyClass is an instance of MyMeta.
print(type(MyClass)) # Output: <class '__main__.MyMeta'>

7.9.2.2 - Overriding Metaclass Methods

By overriding methods like __new__ and __init__, a metaclass can customize class creation and initialization.

  • __new__: Called before __init__. It creates the class object.
  • __init__: Used to initialize the newly created class object.

7.9.2.3 - The Metaclass __call__ Method

The __call__ method in metaclasses controls what happens when the class is called (i.e., when its instances are created). Overriding this method can change how class instances are created.


7.9.3 - Metaclasses in Action

7.9.3.1 - Controlling Class Creation

Metaclasses can intervene at the time of class creation. For instance, they can enforce certain class attributes or methods to be defined, or prevent the creation of a class based on certain criteria.

7.9.3.2 - Altering Class Behavior

Beyond creation, metaclasses can alter the behavior of a class. They can modify or add methods and attributes or define class-level behavior that is inherited across subclasses.

7.9.3.3 - Use Cases of Metaclasses

While metaclasses can be a powerful tool, they are often best reserved for solving specific problems in advanced programming scenarios. These include:

  • Implementing Singleton patterns.
  • Creating APIs that require classes to conform to specific structures.
  • Adding automatic property validation or serialization.
  • Framework development where classes need to be registered or modified at creation.

7.9.4 - Metaclasses Case Studies

7.9.4.1 - Case Study 1: Singleton Pattern

Using a metaclass to create a Singleton, a design pattern that restricts a class to a single instance.

class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]

class Singleton(metaclass=SingletonMeta):
pass

# All instances will be the same object
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2) # Output: True

7.9.4.2 - Case Study 2: Validating Attributes

A metaclass for validating attributes of a class upon creation.

class ValidateMeta(type):
def __new__(cls, name, bases, dct):
if 'attr' not in dct:
raise TypeError("Missing attribute 'attr'")
return super().__new__(cls, name, bases, dct)

class MyClass(metaclass=ValidateMeta):
attr = 5

# Uncommenting below line will raise a TypeError
# class MyInvalidClass(metaclass=ValidateMeta): pass

7.9.4.3 - Case Study 3: Registering Subclasses

Automatically registering subclasses in a base class.

class RegistryMeta(type):
registry = {}

def __new__(cls, name, bases, dct):
new_class = super().__new__(cls, name, bases, dct)
cls.registry[new_class.__name__] = new_class
return new_class

class Base(metaclass=RegistryMeta):
pass

class SubclassA(Base):
pass

print(RegistryMeta.registry) # Output: {'SubclassA': <class '__main__.SubclassA'>}

7.9.4.4 - Case Study 4: Enforcing Class Properties

Enforcing a specific property or method in subclass implementations.

class EnforceMethodsMeta(type):
def __new__(cls, name, bases, dct):
if 'required_method' not in dct:
raise TypeError(f"Missing implementation of required_method in {name}")
return super().__new__(cls, name, bases, dct)

class AbstractBase(metaclass=EnforceMethodsMeta):
pass

# Uncommenting below line will raise a TypeError
# class InvalidSubclass(AbstractBase): pass

class ValidSubclass(AbstractBase):
def required_method(self):
pass

7.9.4.5 - Case Study 5: Dynamically Altering Class Members

Using a metaclass to dynamically add or alter methods and

properties of a class.

class AlterClassMeta(type):
def __new__(cls, name, bases, dct):
dct['added_attribute'] = 'This is an added attribute'
return super().__new__(cls, name, bases, dct)

class MyClass(metaclass=AlterClassMeta):
pass

print(MyClass.added_attribute) # Output: This is an added attribute