Understanding metaclass In  Python

Understanding metaclass In Python

Let's say we define a string

some_string = "I am some_string"
print(some_string.__class__)

As expected the result should print str as some_string is indeed a string
but can you guess what the below result is?

print(some_string.__class__.__class__)

The result for the above code will be type and even if you keep going down the rabbit hole

print(some_string.__class__.__class__.__class__.__class__.__class__.__class__.__class__.__class__)

The output will still be type, this is because everything in python is an object, and a class is just a blueprint that defines how the instance of the class will behave.

class MyClass:
    pass

So when you create a class you are defining an object that has the ability to create other objects (instances).
What if I tell you a class is also an instance of another type of class called a Metaclass?

Introduction to metaclass

A Metaclass is a class that defines the behavior of other classes. The same way, a class is a blueprint for objects(instances) a metaclass is also a blueprint for classes.

Wonders of "type"

There is a default metaclass and we are all familiar with it. It is called type . That's right that simple function we use to determine what the type of an object is is a metaclass

type(name, bases, attrs)

Where:
name: name of the class
bases: tuple of the parent class (for an inheritance, can be empty)
attrs: dictionary containing attributes names and values

Below is a practical example

MyClass = type('MyClass', (), {})
print(MyClass.__class__)

MyClass.__class__ in this case, should resolve to <class 'type'> which if we also tried str.__class__ (str being the built-in python string class) should also resolve to a <class 'type'>

So when we do

some_string = "I am some_string"

we are creating an instance of a class str() which in turn is an instance of the <class 'type'> which explains why some_string.__class__.__class__ results to a <class 'type'>

Metaclasses are often used in Python frameworks and libraries to provide a way to customize the behavior of classes without having to modify the classes themselves.

The common use cases for metaclass include

  • Enforcing certain constraints at the class level, such as ensuring that a class has a particular attribute or that all subclasses of a certain class meet certain requirements.

  • Automatically registering classes with a registry or factory, without having to manually register each class.

  • Customizing the way classes are created, such as by adding methods or attributes to the class automatically.

We can customize the class creation process by passing the metaclass keyword in the class definition. This can also be done by inheriting a class that has already passed in this keyword.

class MyMeta(type):
    pass

class MyClass(metaclass=MyMeta):
    pass

class MySubclass(MyClass):
    pass

print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))

We can see that the type of MyMeta class is type and that the type of MyClass and MySubClass is MyMeta.

When defining a class, if no metaclass is defined the default type metaclass will be used. If a metaclass is given and it is not an instance of type(), then it is used directly as the metaclass.