Metaclasses

Difficulty: Advanced

Question

What are metaclasses in Python? When would you use them?

Answer

A metaclass is the class of a class. Just as a class defines how instances behave, a metaclass defines how classes behave.

In Python, 'type' is the default metaclass. When you write 'class Foo:', Python calls type('Foo', bases, namespace) to create the class.

Metaclass use cases: - Enforcing class contracts (required methods/attributes) - Automatic registration of subclasses - Modifying class creation (adding methods, changing attributes) - ORM field mapping (Django models, SQLAlchemy) - Singleton pattern

Modern alternatives: __init_subclass__ (Python 3.6+) and class decorators handle most metaclass use cases more simply.

Code examples

Understanding type() as a Metaclass

# Everything is an object, and every object has a type
print(type(42))          # <class 'int'>
print(type('hello'))     # <class 'str'>
print(type(int))         # <class 'type'>
print(type(type))        # <class 'type'> (type is its own metaclass)

# Classes are instances of type
class MyClass:
    x = 10
    def greet(self):
        return 'hello'

# The above is equivalent to:
MyClass2 = type('MyClass2', (), {'x': 10, 'greet': lambda self: 'hello'})

print(type(MyClass))     # <class 'type'>
print(MyClass.x)         # 10
print(MyClass2.x)        # 10

# Inheritance
class Child(MyClass):
    pass

Child2 = type('Child2', (MyClass,), {})
print(Child2().greet())  # 'hello'

type() is both a function (returns type of object) and a class (the metaclass of all classes). All classes are instances of type.

Custom Metaclass

# Metaclass that enforces required methods
class InterfaceMeta(type):
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        
        # Skip check for the base class itself
        if bases:  # Only check subclasses
            required = getattr(cls, '_required_methods', [])
            for method in required:
                if method not in namespace:
                    raise TypeError(
                        f"{name} must implement {method}()"
                    )
        return cls

class Plugin(metaclass=InterfaceMeta):
    _required_methods = ['execute', 'cleanup']

# This works:
class GoodPlugin(Plugin):
    def execute(self):
        return 'executing'
    def cleanup(self):
        return 'cleaning'

print(GoodPlugin().execute())  # 'executing'

# This raises TypeError at class definition time:
# class BadPlugin(Plugin):
#     def execute(self):
#         return 'executing'
#     # Missing cleanup()!
# TypeError: BadPlugin must implement cleanup()

The metaclass __new__ is called when the CLASS is created (not when instances are created). This allows validating the class definition itself.

__init_subclass__: Modern Alternative

# __init_subclass__ (Python 3.6+) - simpler than metaclasses
class Plugin:
    _registry = {}
    
    def __init_subclass__(cls, plugin_name=None, kwargs):
        super().__init_subclass__(kwargs)
        name = plugin_name or cls.__name__.lower()
        Plugin._registry[name] = cls
        print(f"Registered plugin: {name}")

class JsonPlugin(Plugin, plugin_name='json'):
    def process(self):
        return 'Processing JSON'

class CsvPlugin(Plugin, plugin_name='csv'):
    def process(self):
        return 'Processing CSV'

class XmlPlugin(Plugin):  # Uses class name as default
    def process(self):
        return 'Processing XML'

print(Plugin._registry)
# {'json': <class 'JsonPlugin'>, 'csv': <class 'CsvPlugin'>, 'xmlplugin': <class 'XmlPlugin'>}

# Factory pattern using registry
def get_plugin(name):
    cls = Plugin._registry.get(name)
    if cls:
        return cls()
    raise ValueError(f"Unknown plugin: {name}")

plugin = get_plugin('json')
print(plugin.process())  # 'Processing JSON'

__init_subclass__ is called every time a class is subclassed. It handles 90% of metaclass use cases (registration, validation) without the complexity.

Key points

Concepts covered

Metaclasses, type, __new__, __init_subclass__, Class Creation