#1 Data Analytics Program in India
₹2,499₹1,499Enroll Now
9 min read
•Question 28 of 41hard

Metaclasses in Python

Understanding the class of a class.

What You'll Learn

  • What metaclasses are and how they work
  • The relationship between classes and type
  • Creating custom metaclasses
  • Practical use cases: singletons, registries, validation
  • When to use metaclasses vs alternatives

Understanding Metaclasses

In Python, everything is an object, including classes. A metaclass is the "class of a class" — it defines how classes themselves behave.

code.pyPython
# The hierarchy:
# instance → class → metaclass

class MyClass:
    pass

obj = MyClass()

print(type(obj))        # <class '__main__.MyClass'>
print(type(MyClass))    # <class 'type'>
print(type(type))       # <class 'type'>

# 'type' is:
# 1. The default metaclass for all classes
# 2. Its own metaclass (type's type is type)
# 3. The way to create classes dynamically

Creating Classes with type()

code.pyPython
# Normal class definition
class Dog:
    species = "Canis familiaris"

    def bark(self):
        return "Woof!"

# Equivalent using type()
def bark(self):
    return "Woof!"

Dog = type(
    'Dog',                      # Class name
    (),                         # Base classes (tuple)
    {                           # Namespace (dict)
        'species': 'Canis familiaris',
        'bark': bark
    }
)

# Both create identical classes
d = Dog()
print(d.bark())  # "Woof!"

Creating a Custom Metaclass

code.pyPython
class Meta(type):
    def __new__(mcs, name, bases, namespace):
        """Called when a CLASS is created (not instance)."""
        print(f"Creating class: {name}")
        print(f"Bases: {bases}")
        print(f"Namespace keys: {list(namespace.keys())}")
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace):
        """Called after class is created."""
        print(f"Initializing class: {name}")
        super().__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        """Called when class is instantiated."""
        print(f"Creating instance of {cls.__name__}")
        return super().__call__(*args, **kwargs)

class MyClass(metaclass=Meta):
    x = 10

    def method(self):
        pass

# Output when class is defined:
# Creating class: MyClass
# Bases: ()
# Namespace keys: ['__module__', '__qualname__', 'x', 'method']
# Initializing class: MyClass

obj = MyClass()
# Output: Creating instance of MyClass

Practical Use Cases

Singleton Pattern

code.pyPython
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 Database(metaclass=SingletonMeta):
    def __init__(self):
        print("Initializing database connection")
        self.connected = True

db1 = Database()  # Initializing database connection
db2 = Database()  # (nothing printed - cached)

print(db1 is db2)  # True

Plugin Registry

code.pyPython
class PluginRegistry(type):
    plugins = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:  # Don't register the base class
            mcs.plugins[name] = cls
        return cls

class Plugin(metaclass=PluginRegistry):
    """Base class for all plugins."""
    pass

class ImagePlugin(Plugin):
    def process(self): return "Processing image"

class VideoPlugin(Plugin):
    def process(self): return "Processing video"

class AudioPlugin(Plugin):
    def process(self): return "Processing audio"

# All plugins auto-registered
print(PluginRegistry.plugins)
# {'ImagePlugin': <class 'ImagePlugin'>, 'VideoPlugin': ...}

# Use dynamically
def process_file(plugin_name):
    plugin_cls = PluginRegistry.plugins[plugin_name]
    return plugin_cls().process()

Attribute Validation

code.pyPython
class ValidatedMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Ensure all methods have docstrings
        for key, value in namespace.items():
            if callable(value) and not key.startswith('_'):
                if not value.__doc__:
                    raise TypeError(f"Method {key} must have docstring")

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

class APIHandler(metaclass=ValidatedMeta):
    def get(self):
        """Handle GET requests."""
        pass

    def post(self):
        """Handle POST requests."""
        pass

    # def delete(self):  # Would raise TypeError - no docstring!
    #     pass

Abstract Base Class Pattern

code.pyPython
class InterfaceMeta(type):
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)

        # Check if abstract methods are implemented
        if bases:  # Not the base interface itself
            for base in bases:
                if hasattr(base, '_abstract_methods'):
                    for method in base._abstract_methods:
                        if method not in namespace:
                            raise TypeError(
                                f"Class {name} must implement {method}"
                            )
        return cls

class Serializable(metaclass=InterfaceMeta):
    _abstract_methods = ['serialize', 'deserialize']

class JSONData(Serializable):
    def serialize(self): return "{}"
    def deserialize(self, data): pass

# class BrokenData(Serializable):  # TypeError!
#     def serialize(self): pass
#     # Missing deserialize

Metaclasses vs Alternatives

FeatureMetaclassClass Decoratorinit_subclass
ControlFull class creationPost-creation modificationSubclass hook
ComplexityHighMediumLow
InheritanceAutomaticMust reapplyAutomatic
Use CaseDeep customizationSimple modificationsSubclass validation
code.pyPython
# __init_subclass__ - simpler alternative (Python 3.6+)
class Plugin:
    plugins = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin.plugins[cls.__name__] = cls

class ImagePlugin(Plugin):
    pass

print(Plugin.plugins)  # {'ImagePlugin': <class 'ImagePlugin'>}

Interview Tip

When asked about metaclasses:

  1. "type" is the default metaclass; classes are instances of type
  2. new creates the class, call creates instances
  3. Use cases: singletons, registries, validation, ORMs
  4. Consider simpler alternatives: decorators, init_subclass
  5. "If you're not sure if you need a metaclass, you don't"