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 3.x
# Here __metaclass__ points to the metaclass object. class ExampleClass(metaclass=type): pass
Python 2.x
# Here __metaclass__ points to the metaclass object. class ExampleClass(object): __metaclass__ = type pass
When a class
is created, the interpreter:
- Gets the name of the
class
. - Gets the base classes of the
class
. - Gets the
metaclass
of theclass
. If it is defined, it will use this first. Otherwise, it will check in the base classes for themetaclass
. It it can’t find ametaclass
in thebase class
, thetype
object
is used instead. - Gets the variables/attributes in the
class
and stores them as a dictionary. - Passes this information to
metaclass
asmetaclass(name_of_class, base_classes, attributes_dictionary)
and it returns aclass
object.
For example:
# type(name, base, attrs) # name is the name of the class # base is a tuple of base classes (all methods/attributes are inherited # from these) attrs is a dictionary filled with the class attributes classObject = type('ExampleClass', (object,) ,{})
- Python Programming – Classes and Objects
- Python Programming – Built-in Class Attributes
- How to Check for Object Type in Python | Python type() and isinstance() Function with Examples
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
class a: def __init__(self, data): self.data = data def getd3(self): return self.data * 3 class MyMeta(type): def __new__(metaname, classname, baseclasses, attrs): print('New called with') print('metaname', metaname) print('classname', classname) print('baseclasses', baseclasses) print('attrs', attrs) attrs['getdata'] = a.__dict__['getd3'] # attrs['getdata'] = a.getd3 return type.__new__(metaname, classname, baseclasses, attrs) def __init__(classobject, classname, baseclasses, attrs): print('init called with') print('classobject', classobject) print('classname', classname) print('baseclasses', baseclasses) print('attrs', attrs) class Kls(metaclass=MyMeta): def __init__(self,data): self.data = data def printd(self): print(self.data) ik = Kls('arun') ik.printd() print(ik.getdata())
Python 2.x
class a(object): def __init__(self, data): self.data = data def getd3(self): return self.data * 3 class MyMeta(type): def __new__(metaname, classname, baseclasses, attrs): print 'New called with' print 'metaname', metaname print 'classname', classname print 'baseclasses', baseclasses print 'attrs', attrs attrs['getdata'] = a.__dict__['getd3'] # attrs['getdata'] = a.getd3 return type.__new__(metaname, classname, baseclasses, attrs) def __init__(classobject, classname, baseclasses, attrs): print 'init called with' print 'classobject', classobject print 'classname', classname print 'baseclasses', baseclasses print 'attrs', attrs class Kls(object): __metaclass__ = MyMeta def __init__(self, data): self.data = data def printd(self): print self.data ik = Kls('arun') ik.printd() print ik.getdata()
When running the code, we get:
New called with metaname <class '__main__.MyMeta'> classname Kls baseclasses (<type 'object'>,) attrs {'__module__': '__main__', '__metaclass__': <class '__main__.MyMeta'>, 'printd': <function printd at 0x7fbdab0176e0>, '__init__': <function __init__ at 0x7fbdab017668>} init called with classobject <class '__main__.Kls'> classname Kls baseclasses (<type 'object'>,) attrs {'__module__': '__main__', 'getdata': <function getd3 at 0x7fbdab017500>, '__metaclass__': <class '__main__.MyMeta'>, 'printd': <function printd at 0x7fbdab0176e0>, '__init__': <function __init__ at 0x7fbdab017668>} arun 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
def meta_func(name, bases, attrs): print('meta function called with', name, bases, attrs) nattrs = {'mod' + key:attrs[key] for key in attrs} return type(name, bases, nattrs) MyMeta = meta_func class Kls(metaclass=MyMeta): def setd(self, data): self.data = data def getd(self): return self.data k = Kls() k.modsetd('arun') print(k.modgetd())
Python 2.x
def meta_func(name, bases, attrs): print 'meta function called with', name, bases, attrs nattrs = {'mod' + key:attrs[key] for key in attrs} return type(name, bases, nattrs) MyMeta = meta_func class Kls(object): __metaclass__ = MyMeta def setd(self, data): self.data = data def getd(self): return self.data k = Kls() k.modsetd('arun') print k.modgetd()
Gives us the following output:
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>} 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
class MyMeta(type): def __call__(clsname, *args): print('MyMeta called with') print('clsname:', clsname) print('args:', args) instance = object.__new__(clsname) instance.__init__(*args) return instance class Kls(metaclass=MyMeta): def __init__(self, data): self.data = data def printd(self): print(self.data) ik = Kls('arun') ik.printd()
Python 2.x
class MyMeta(type): def __call__(clsname, *args): print 'MyMeta called with' print 'clsname:', clsname print 'args:' ,args instance = object.__new__(clsname) instance.__init__(*args) return instance class Kls(object): __metaclass__ = MyMeta def __init__(self,data): self.data = data def printd(self): print self.data ik = Kls('arun') ik.printd()
The output is as follows:
MyMeta called with clsname: <class '__main__.Kls'> args: ('arun',) 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:
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
class SuperMeta(type): def __call__(metaname, clsname, baseclasses, attrs): print('SuperMeta Called') clsob = type.__new__(metaname, clsname, baseclasses, attrs) type.__init__(clsob, clsname, baseclasses, attrs) return clsob class MyMeta(type, metaclass=SuperMeta): def __call__(cls, *args, **kwargs): print('MyMeta called', cls, args, kwargs) ob = object.__new__(cls, *args) ob.__init__(*args) return ob print('create class') class Kls(metaclass=MyMeta): def __init__(self, data): self.data = data def printd(self): print(self.data) print('class created') ik = Kls('arun') ik.printd() ik2 = Kls('avni') ik2.printd()
Python 2.x
class SuperMeta(type): def __call__(metaname, clsname, baseclasses, attrs): print 'SuperMeta Called' clsob = type.__new__(metaname, clsname, baseclasses, attrs) type.__init__(clsob, clsname, baseclasses, attrs) return clsob class MyMeta(type): __metaclass__ = SuperMeta def __call__(cls, *args, **kwargs): print 'MyMeta called', cls, args, kwargs ob = object.__new__(cls, *args) ob.__init__(*args) return ob print 'create class' class Kls(object): __metaclass__ = MyMeta def __init__(self, data): self.data = data def printd(self): print self.data print 'class created' ik = Kls('arun') ik.printd() ik2 = Kls('avni') ik2.printd()
Gives us the following output:
create class SuperMeta Called class created MyMeta called class '__main__.Kls' ('arun',) {} arun MyMeta called <class '__main__.Kls' ('avni',) {} avni