What are metaclasses in Python?

A class determines how object will behave. In Python, every type is defined by a class. The primitive data types used in other languages like int, char etc are object of int class, str class respectively. And we can make a new type by creating a class of that type.

In Python, class is itself instance of a metaclass. Metaclasses are an esoteric concept (used and understood by very less Python programmers) that defines a class’s behavior.

In Python, classes are object as well. type is a metaclass, of which classes are instances and type is also instance of type class.

An example to illustrate concept of metaclass is given as follows:


#construct a class named Fruit that does nothing
class Fruit:
   pass
		
#object of class Fruit
apple = Fruit()

# apple is object of class fruit which is 
# demonstrated by type() function

type(apple)

>>> <class '__main__.Fruit'>

#class Fruit is also an instance of metaclass type
type(Fruit)

>>> <class 'type'>

#the metaclass is also an instance of its own class type

type(type)

>>> <class 'type'>

In the above case:

  1. apple is an instance of class Fruit
  2. Fruit is an instance class of the type metaclass
  3. type is also an instance of the type metaclass, so it is an instance of itself

Just like a class Fruit being instance of metaclass type, every other objects are also instance of metaclass type. Like integer is also an instance of class type. And type is always a instance of type class itself.

Real Life Scenario

The metaclass is used in nested serializer in Django Rest Framework as a real life implementation.

We shall be discussing the creation of singleton classes in Python as real life example of metaclasses.

Singleton classes are those classes designed to restrict instantiations of a class to one object. It is used in the case where exactly one object is needed. The concept can be generalized to restrict the instantiation to a certain or fixed number of objects. For example:


#making a singleton class
class Singleton(type):
#we form zero instances for the class
	_instances = {}
	#function to ensure no extra instances are made except single one
	
	def __call__(cls, *args, **kwargs):
		if cls not in cls._instances:
		
		#allocate a same address for the object even if more than one are made
		cls._instances[cls]=super(Singleton,cls).__call__(*args,**kwargs)
		
		return cls._instances[cls]

#we make metaclass for Singleton class as made above
class SingletonClass(metaclass=Singleton):
	pass

#make x and y as the instance of SingletonClass
x = SingletonClass()
y = SingletonClass()
# both object are treated as same by allocating a same address i.e. no separate instances are being created. So both the objects are same

print(x==y)

>>>True