Skip to main content

Metaclasses

Metaprogramming

Metaprogramming is a programming technique in which code writes, modifies, or analyzes other code (or itself) at runtime or compile-time. It allows developers to create highly flexible and reusable software by treating code as data.

Python suppoorts a form of metaprogramming for classes called metaclasses, which are classes that define the behavior of other classes.

Class as Object

To understand metaclasses, it's essential to first have a solid grasp of what classes and objects are.

A class serves as a blueprint for creating objects, defining their structure and behavior. An object, on the other hand, is an instance of that blueprint, representing a specific realization of the class's structure.

In Python, classes themselves are objects. This means they are instances of a metaclass, which governs their creation and behavior
class MyClass:
attr = "I am a class attribute"

# Create an object of the class
obj = MyClass()

print(obj.attr) # Accessing an attribute via the object

# Check if the class itself is an object
print(type(MyClass)) # Output: <class 'type'>

# Check if the object is an instance of the class
print(isinstance(obj, MyClass)) # Output: True

In this example, MyClass is a class that defines a blueprint, while obj is an instance of that blueprint.

Notably, the class MyClass itself is an instance of the metaclass type, demonstrating that classes in Python are objects as well.

About metaclass

A metaclass is a class of a class that defines how a class behaves. While classes define the behavior of objects, metaclasses define the behavior of classes themselves. This means a metaclass can control the creation, modification, and behavior of classes.

How it works

When we create a class in Python, Python internally uses a metaclass to create that class. By default, this metaclass is type. For example:

class MyClass:
pass

print(type(MyClass)) #Output: <class 'type'>

In this case, MyClass is an instance of the metaclass type

Defining a metaclass

A metaclass can be defined by subclassing the type class. A metaclass typically overrides these methods:

  • __new__ : Called before an object is created; used to control the creation of a class.
  • __init__ : Called to initialize the newly created class.
class MyMetaclass(type):
def __new__(cls, name, bases, attrs):
print(f"Creating class {name}")
return super().__new__(cls, name, bases, attrs)

class MyClass(metaclass=MyMetaclass):
pass

Here, MyMeta is a metaclass that modifies the class creation process by printing a message whenever a class is created.

Why do we need it?

Metaclasses are used when we need to control or customise the behaviour of classes themselves

  1. Enforce Coding Standards

Metaclasses can be used to ensure that all classes in a module adhere to certain conventions or contain specific attributes

class EnforceAttributesMeta(type):
def __new__(cls, name, bases, dct):
if "required_attribute" not in dct:
raise AttributeError(f"Class {name} must define 'required_attribute'")
return super().__new__(cls, name, bases, dct)

class MyClass(metaclass=EnforceAttributesMeta):
required_attribute = 42
  1. Automatically Add Methods or Attributes

Metaclasses can dynamically add methods or attributes to classes at the time of their creation

class AutoMethodMeta(type):
def __new__(cls, name, bases, dct):
dct["auto_method"] = lambda self: "This method was added automatically!"
return super().__new__(cls, name, bases, dct)

class MyClass(metaclass=AutoMethodMeta):
pass

obj = MyClass()
print(obj.auto_method()) # Output: This method was added automatically!
  1. Customize Class Inheritance

Metaclasses can enforce rules on class hierarchies or modify inheritance behavior

  1. Dynamic Class creation

Metaclasses allow programmer to create classes programmatically. For example, one can define classes dynamically based on external configuration or input