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:
x
is an instance of classFoo
.Foo
is an instance of thetype
metaclass.type
is also an instance of thetype
metaclass, 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 theattrs
dictionary 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.↩︎