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