Friday, July 29, 2016

How Metaclasses work technically in Python 2 and 3

A metaclass is a class/object which defines a type/class of other classes. In Python a metaclass can be a class, function or any object that supports calling an interface. This is because to create a class object; its metaclass is called with the class name, base classes and attributes (methods). When no metaclass is defined (which is usually the case), the default metaclass type is used.

For example:

Python 2.x
  1. # Here __metaclass__ points to the metaclass object.
  2. class ExampleClass(object):
  3.     __metaclass__ = type
  4.     pass
Python 3.x
  1. # Here __metaclass__ points to the metaclass object.
  2. class ExampleClass(metaclass=type):
  3.     pass
When a class is created, the interpreter:
  1. Gets the name of the class.
  2. Gets the base classes of the class.
  3. Gets the metaclass of the class. If it is defined, it will use this first. Otherwise, it will check in the base classes for the metaclass. It it can't find a metaclass in the base class, the type object is used instead.
  4. Gets the variables/attributes in the class and stores them as a dictionary.
  5. Passes this information to metaclass as metaclass(name_of_class, base_classes, attributes_dictionary) and it returns a class object.
For example:
  1. # type(name, base, attrs)
  2. # name is the name of the class
  3. # base is a tuple of base classes (all methods/attributes are inherited
  4. # from these) attrs is a dictionary filled with the class attributes
  5. classObject = type('ExampleClass', (object,) ,{})
When type is called, its __call__ method is called. This method in turn calls the __new__ and __init__ methods. The __new__ method creates a new object, whereas the __init__ method initializes it. We can easily play with methods. This is a working example:

Python 3.x
  1. class a:
  2.     def __init__(self, data):
  3.         self.data = data
  4.     def getd3(self):
  5.         return self.data * 3
  6. class MyMeta(type):
  7.     def __new__(metaname, classname, baseclasses, attrs):
  8.         print('New called with')
  9.         print('metaname', metaname)
  10.         print('classname', classname)
  11.         print('baseclasses', baseclasses)
  12.         print('attrs', attrs)
  13.         attrs['getdata'] = a.__dict__['getd3']
  14.         # attrs['getdata'] = a.getd3
  15.         return type.__new__(metaname, classname, baseclasses, attrs)
  16.     def __init__(classobject, classname, baseclasses, attrs):
  17.         print('init called with')
  18.         print('classobject', classobject)
  19.         print('classname', classname)
  20.         print('baseclasses', baseclasses)
  21.         print('attrs', attrs)
  22. class Kls(metaclass=MyMeta):
  23.     def __init__(self,data):
  24.         self.data = data
  25.     def printd(self):
  26.         print(self.data)
  27. ik = Kls('arun')
  28. ik.printd()
  29. print(ik.getdata())
Python 2x
  1. class a(object):
  2.     def __init__(self, data):
  3.         self.data = data 
  4.     def getd3(self):
  5.         return self.data * 3
  6. class MyMeta(type):
  7.     def __new__(metaname, classname, baseclasses, attrs):
  8.         print 'New called with'
  9.         print 'metaname', metaname
  10.         print 'classname', classname
  11.         print 'baseclasses', baseclasses
  1.         print 'attrs', attrs
  2.         attrs['getdata'] = a.__dict__['getd3']
  3.         # attrs['getdata'] = a.getd3
  4.         return type.__new__(metaname, classname, baseclasses, attrs)
  5.     def __init__(classobject, classname, baseclasses, attrs):
  6.         print 'init called with'
  7.         print 'classobject', classobject
  8.         print 'classname', classname
  9.         print 'baseclasses', baseclasses
  10.         print 'attrs', attrs 
  11. class Kls(object):
  12.     __metaclass__ = MyMeta
  13.      def __init__(self, data):
  14.       self.data = data
  15.     def printd(self):
  16.         print self.data
  17. ik = Kls('arun')
  18. ik.printd()
  19. print ik.getdata()
When running the code, we get:
  1. New called with
  2. metaname <class '__main__.MyMeta'>
  3. classname Kls
  4. baseclasses (<type 'object'>,)
  5. attrs {'__module__': '__main__', '__metaclass__': <class '__main__.MyMeta'>, 'printd': <function printd at 0x7fbdab0176e0>, '__init__': <function __init__ at 0x7fbdab017668>}
  6. init called with
  7. classobject <class '__main__.Kls'>
  8. classname Kls
  9. baseclasses (<type 'object'>,)
  10. attrs {'__module__': '__main__', 'getdata': <function getd3 at 0x7fbdab017500>, '__metaclass__': <class '__main__.MyMeta'>, 'printd': <function printd at 0x7fbdab0176e0>, '__init__': <function __init__ at 0x7fbdab017668>}
  11. arun
  12. arunarunarun
Normally we need to override only one method __new__ or __init__. We can also use function instead of a class. Here is an example:

Python 3.x
  1. def meta_func(name, bases, attrs):
  2.     print('meta function called with', name, bases, attrs)
  3.     nattrs = {'mod' + key:attrs[key] for key in attrs}
  4.     return type(name, bases, nattrs)
  5. MyMeta = meta_func 
  6. class Kls(metaclass=MyMeta):
  7.     def setd(self, data):
  8.         self.data = data
  9.     def getd(self):
  10.         return self.data 
  11. k = Kls()
  12. k.modsetd('arun')
  13. print(k.modgetd()
Python 2.x
  1. def meta_func(name, bases, attrs):
  2.     print 'meta function called with', name, bases, attrs
  3.     nattrs = {'mod' + key:attrs[key] for key in attrs}
  4.     return type(name, bases, nattrs)
  5. MyMeta = meta_func
  6. class Kls(object):
  7.     __metaclass__ = MyMeta
  8.     def setd(self, data):
  9.             self.data = data
  10.     def getd(self):
  11.             return self.data
  12. k  = Kls()
  13. k.modsetd('arun')
  14. print k.modgetd()
Gives us the following output:
  1. meta function called with  Kls (<type 'object'>,) {'setd': <function setd at 0x88b21ec>, 'getd': <function getd at 0x88b22cc>, '__module__': '__main__', '__metaclass__': <function meta_func at 0xb72341b4>}
  2. arun
Other then modifying base classes and methods of classes to be created, metaclasses can also modify instance creation process. This is because when we create an instance (ik = Kls()), this is like calling the class Kls. One point to note is that whenever we call an object its type's __call__ method is called. So in this case the class type is metaclass hence its __call__ method will be called. We can check like this:

Python 3.x
  1. class MyMeta(type):
  2.     def __call__(clsname, *args):
  3.         print('MyMeta called with')
  4.         print('clsname:', clsname)
  5.         print('args:', args)
  6.         instance =  object.__new__(clsname)
  7.         instance.__init__(*args)
  8.         return instance
  9. class Kls(metaclass=MyMeta):
  10.     def __init__(self, data):
  11.         self.data = data
  12.     def printd(self):
  13.         print(self.data)
  14. ik = Kls('arun')
  15. ik.printd()
Python 2.x
  1. class MyMeta(type):
  2.     def __call__(clsname, *args):
  3.         print 'MyMeta called with'
  4.         print 'clsname:', clsname
  5.         print 'args:' ,args
  6.         instance =  object.__new__(clsname)
  7.         instance.__init__(*args)
  8.         return instance
  9. class Kls(object):
  10.     __metaclass__ = MyMeta
  11.     def __init__(self,data):
  12.         self.data = data
  13.     def printd(self):
  14.         print self.data
  15. ik = Kls('arun')
  16. ik.printd()
The output is as follows:
  1. MyMeta called with
  2. clsname: <class '__main__.Kls'>
  3. args: ('arun',)
  4. arun
Equipped with this information, if we go to the start of our discussion about the class creation process, it ended with a call to the metaclass object, which provided a class object. It was like this:
  1. Kls = MetaClass(name, bases, attrs)
Hence this call should call the metaclass's type. The metaclass type is the metaclass's metaclass! We can check this as follows:

Python 3.x
  1. class SuperMeta(type):
  2.     def __call__(metaname, clsname, baseclasses, attrs):
  3.         print('SuperMeta Called')
  4.         clsob = type.__new__(metaname, clsname, baseclasses, attrs)
  5.         type.__init__(clsob, clsname, baseclasses, attrs)
  6.         return clsob
  7. class MyMeta(type, metaclass=SuperMeta):
  8.     def __call__(cls, *args, **kwargs):
  9.         print('MyMeta called', cls, args, kwargs)
  10.         ob = object.__new__(cls, *args)
  11.         ob.__init__(*args)
  12.         return ob
  13. print('create class')
  14. class Kls(metaclass=MyMeta):
  15.     def __init__(self, data):
  16.         self.data = data
  17.     def printd(self):
  18.         print(self.data)
  19. print('class created')
  20. ik = Kls('arun')
  21. ik.printd()
  22. ik2 = Kls('avni')
  23. ik2.printd()
Python 2.x
  1. class SuperMeta(type):
  2.     def __call__(metaname, clsname, baseclasses, attrs):
  3.         print 'SuperMeta Called'
  4.         clsob = type.__new__(metaname, clsname, baseclasses, attrs)
  5.       type.__init__(clsob, clsname, baseclasses, attrs)
  6.         return clsob
  7. class MyMeta(type):
  8.     __metaclass__ = SuperMeta
  9.     def __call__(cls, *args, **kwargs):

  10.         print 'MyMeta called', cls, args, kwargs

Gives us the following output:
  1. class SuperMeta(type):
  2.     def __call__(metaname, clsname, baseclasses, attrs):
  3.       print 'SuperMeta Called'
  4.         clsob = type.__new__(metaname, clsname, baseclasses, attrs)
  5.         type.__init__(clsob, clsname, baseclasses, attrs)
  6.         return clsob
  7. class MyMeta(type):
  8.     __metaclass__ = SuperMeta
  9.     def __call__(cls, *args, **kwargs):
  10.         print 'MyMeta called', cls, args, kwargs
  11.         ob = object.__new__(cls, *args)
  12.         ob.__init__(*args)
  13.         return ob
  14. print 'create class'
  15. class Kls(object):
  16.     __metaclass__ = MyMeta
  17.     def __init__(self, data):
  18.         self.data = data
  19.     def printd(self):
  20.         print self.data
  21. print 'class created' 
  22. ik = Kls('arun')
  23. ik.printd()
  24. ik2 = Kls('avni')
  25. ik2.printd()create class
  26. SuperMeta Called
  27. class created
  28. MyMeta called class '__main__.Kls' ('arun',) {}
  29. arun
  30. MyMeta called &lt;class '__main__.Kls' ('avni',) {}
  31. avni
Source:pythoncentral
If you feel useful for you and for everyone, please share it!
Suggest for you:

Learn Python for Beginners!

Python, Ruby, Shell - Scripting for Beginner

Learning Python for Data Analysis and Visualization

Complete Python Bootcamp (Hot)

The Python Mega Course: Build 10 Python Applications