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 behaviorclass 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
- 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
- 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!
- Customize Class Inheritance
Metaclasses can enforce rules on class hierarchies or modify inheritance behavior
- Dynamic Class creation
Metaclasses allow programmer to create classes programmatically. For example, one can define classes dynamically based on external configuration or input