Saturday 31 December 2016

What is a metaclass in Python?

A meta-class is the class of a class. Like a class defines how an instance of the class behaves, a meta-class defines how a class behaves. A class is an instance of a meta-class.
Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances.
or meta-class is simply an object that constructs classes.
A meta-class is most commonly used as a class-factory.
Python creates a new class (when it executes the 'class' statement) by calling the metaclass. Combined with the normal __init__ and __new__ methods.
The main purpose of a meta-class is to change the class automatically, when it's created.
meta-classes are the secret sauce that make 'class' work. The default meta-class for a new style object is called 'type'.
meta-classes take 3 args. 'name', 'bases' and 'dict'

Why do we need meta-class?:
1.intercept a class creation
2.modify the class
3.return the modified class
4.You can use OOP. meta-class can inherit from meta-class, override parent methods. meta-classes can even use meta-classes.
5.You can hook on __new__, __init__ and __call__. Which will allow you to do different stuff. Even if usually you can do it all in __new__, some people are just more comfortable using __init__.
6.One use for meta-classes is adding new properties and methods to an instance automatically.

Example:
class Bus(object):
    def __init__(self, make, model, year, color):
        self.make = make
        self.model = model
        self.year = year
        self.color = color

    @property
    def description(self):
        """ Return a description of this bus. """

        return "%s %s %s %s" % (self.color, self.year, self.make, self.model)

At run time, Bus itself is an instance of type. The source code of the Bus class, shown above, does not include such details as the size in bytes of Bus objects, their binary layout in memory, how they are allocated, that the __init__ method is automatically called each time a Bus is created, and so on. These details come into play not only when a new Bus object is created, but also each time any attribute of a Bus is accessed. Here the meta-class - type - controls these details of Bus behavior. They can be overridden by using a different meta-class instead of type.

The above code contains some redundant code to do with the four attributes make, model, year, and color. It is possible to eliminate some of this redundancy using a meta-class. In Python, a meta-class is most easily defined as a subclass of type.

class AttributeInitType(type):
     def __call__(self, *args, **kwargs):
         """ Create a new instance. """

         # First, create the object in the normal default way.
         obj = type.__call__(self, *args)

         # Additionally, set attributes on the new object.
         for name, value in kwargs.items():
             setattr(obj, name, value)

         # Return the new object.
         return obj

This meta-class only overrides object creation. All other aspects of class and object behavior are still handled by type.
Now the class Bus can be rewritten to use this meta-class.

class Bus(object, metaclass=AttributeInitType):
     @property
     def description(self):
         """ Return a description of this car. """
         return " ".join(str(getattr(self, attr, "Unknown"))
                         for attr in self.__slots__)

Bus objects can then be instantiated like this:
new_bus = Bus(make='Volvo', model='AC', year=2010, color='Black')
 old_bus=Bus(make='Ashok Leyland', model='Sleeper', year=2000)