Python Metaclasses
In Python, a metaclass creates and defines the behavior of other classes, since classes themselves are objects.
Sources:
- Demystifying Python Metaclasses: Understanding and Harnessing the Power of Custom Class Creation
- Python Metaclasses by John Sturtz.
Types and classes
In Python 3, the definitions of types and a classes are euqal. So we can refer to an object’s type and its class interchangeably.
1 | n = 5 |
The type of the built-in classes you are familiar with is also type:
1 | >>> for t in int, float, dict, list, tuple: |
For that matter, the type of type is type as well (yes, really):
1 | >>> type(type) |
type is a metaclass, of which classes are instances.
In the above case:
xis an instance of classFoo.Foois an instance of thetypemetaclass.typeis also an instance of thetypemetaclass, so it is an instance of itself.
Defining Metaclasses
In order to define a custom metaclass, you need to define the following methods:
__new__: This method is responsible for creating and returning the new class object. It takes four arguments:cls, the metaclass;name, the name of the new class being created;bases, a tuple of base classes for the new class; andattrs, a dictionary of attributes for the new class. You can use this method to customize the creation of new classes, by modifying theattrsdictionary or performing other custom processing.
__init__: This method is responsible for initializing the new class object after it has been created by__new__. It takes the same arguments as__new__. You can use this method to perform any additional initialization that is required, such as setting default attribute values or performing other custom processing.
Here is an example of a custom metaclass definition that implements both __new__ and __init__ methods:
1 | class MyMeta(type): |
If you do not define the __init__ method in your custom metaclass, the default __init__ method of the type metaclass will be used instead. This default method does not do anything besides calling super().__init__(...) with the same arguments, so if you do not need any additional initialization logic in your metaclass, it is safe to omit the __init__ method.
Use Cases of Metaclasses
One common use case for metaclasses is to add additional methods or attributes to classes that are created with a certain metaclass.
This code example shows how metaclasses can be used to add additional methods or attributes to classes:
1 | class MyMeta(type): |
In this example, the MyMeta metaclass adds a new_attribute and a new_method to any class that uses it as a metaclass.
Enforcing constraints on the creation of classes
1 | class MyMeta(type): |
In this example, the MyMeta metaclass raises a TypeError if a class created with it as a metaclass does not define required_attribute.
Implementing domain-specific languages (DSLs)
Just like how translators take words and phrases from one language and convert them to another language, metaclasses can take a custom syntax specific to a domain and convert it into Python syntax that can be executed. This enables developers to create a DSL specific to their application domain, making it easier to express concepts and perform operations.
This is how a DSL can be implemented using metaclasses in Python:
1 | class DomainSpecificLanguage(type): |
In this example, we define a new metaclass DomainSpecificLanguage that looks for methods in the class that start with "when_". These methods represent event handlers that will be triggered when a corresponding event is received.
The metaclass creates a new method called listen that can be used to listen for events and trigger the corresponding event handlers. This method is added to the class using the new_cls.listen = listen syntax.
Finally, we define a new class called MyDSLClass using the metaclass syntax. This class includes two event handlers: when_hello and when_goodbye. We can use the listen method to trigger these event handlers by passing in the name of the event we want to trigger.
Adding functionality to classes based on decorators or other annotations
Metaclasses can add additional functionality to classes that have been decorated1 or annotated in a certain way. This enables developers to customize the behavior of classes beyond what is possible with regular class definitions.
1 | class MyMeta(type): |
In this example, the MyMeta metaclass looks for methods decorated with the @my_decorator decorator and adds some additional functionality to them.
For python decorators, see my blog post.↩︎