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.