Asked : Nov 17
Viewed : 51 times
In Python, what are metaclasses and what do we use them for?
Nov 17
A metaclass is the class of a class. A class defines how an instance of the class (i.e. an object) behaves while a metaclass defines how a class behaves. A class is an instance of a metaclass.
While in Python you can use arbitrary callables for metaclasses (like Jerub shows), the better approach is to make it an actual class itself. type
is the usual metaclass in Python. type
is itself a class, and it is its own type. You won't be able to recreate something type
purely in Python, but Python cheats a little. To create your own metaclass in Python you really just want to subclass type
.
A metaclass is most commonly used as a class factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass. Combined with the normal __init__
and __new__
methods, metaclasses, therefore, allow you to do 'extra things' when creating a class, like registering the new class with some registry or replacing the class with something else entirely.
When the class
the statement is executed, Python first executes the body of the class
statement as a normal block of code. The resulting namespace (a dict) holds the attributes of the class-to-be. The metaclass is determined by looking at the base classes of the class-to-be (metaclasses are inherited), at the __metaclass__
attribute of the class-to-be (if any), or the __metaclass__
global variable. The metaclass is then called with the name, bases, and attributes of the class to instantiate it.
However, metaclasses actually define the type of a class, not just a factory for it, so you can do much more with them. You can, for instance, define normal methods on the metaclass. These metaclass methods are like class methods in that they can be called on the class without an instance, but they are also not like class methods in that they cannot be called on an instance of the class. type.__subclasses__()
is an example of a method on the type
metaclass. You can also define the normal 'magic' methods, like __add__
, __iter__
and __getattr__
, to implement or change how the class behaves.
Here's an aggregated example of the bits and pieces:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(mcls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
answered Jan 10
The type(obj)
function gets you the type of an object.
The type()
of a class is its metaclass.
To use a metaclass:
class Foo(object):
__metaclass__ = MyMetaClass
type
is its own metaclass. The class of a class is a metaclass-- the body of a class is the arguments passed to the metaclass that is used to construct the class.
Here you can read about how to use metaclasses to customize class construction.
answered Jan 10
A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes. Before we dive deeper into metaclasses, let's get a few concepts out of the way.
class TestClass():
pass
my_test_class = TestClass()
print(my_test_class)
<__main__.TestClass object at 0x7f6fcc6bf908>
type
in Python enables us to find the type of an object. We can proceed to check the type of object we created above.
type(TestClass)
type
type(type)
type
Wait, What just happened? We'd expect the type of object we created above to be class, but it's not. Hold on to that thought. We will cover it further in a few. We also notice that the type of type
itself is type
. It is an instance of type
. Another magical thing that type
does is enable us to create classes dynamically. Let's show how we'd do that below. The DataCamp
class shown below would be created as shown below using type
:
class DataCamp():
pass
DataCampClass = type('DataCamp', (), {})
print(DataCampClass)
print(DataCamp())
<class '__main__.DataCamp'>
<__main__.DataCamp object at 0x7f6fcc66e358>
In the above example DataCamp
is the class name while DataCampClass
is the variable that holds the class reference. When using type
we can pass attributes of the class using a dictionary as shown below:
PythonClass = type('PythonClass', (), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass.start_date, PythonClass.instructor)
print(PythonClass)
August 2018 John Doe
<class '__main__.PythonClass'>
In case we wanted our PythonClass
to inherit from the DataCamp
class we pass it to our second argument when defining the class using type
PythonClass = type('PythonClass', (DataCamp,), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass)
<class '__main__.PythonClass'>
Now that those two concepts are out of the way, we realize that Python creates the classes using a metaclass. We have seen that everything in Python is an object, these objects are created by metaclasses. Whenever we call class
to create a class, there is a metaclass that does the magic of creating the class behind the scenes. We've already seen type
do this in practice above. It is similar to str
that creates strings and int
that creates integers. In Python, the ___class__
attribute enables us to check the type of the current instance. Let's create a string below and check its type.
article = 'metaclasses'
article.__class__
str
We can also check the type using type(article)
.
type(article)
str
When we check the type of str
, we also find out that it's type.
type(str)
type
When we check the type for float
, int
, list
, tuple
, and dict
, we will have a similar output. This is because all of these objects are of type type
.
print(type(list),type(float), type(dict), type(tuple))
<class 'type'> <class 'type'> <class 'type'> <class 'type'>
We've already seen type
creates classes. Hence when we check the __class__
of __class__
it should return type
.
article.__class__.__class__
type
answered Jan 10
Python is an object-oriented language that makes working with classes simple and easy. A class in python is a way to describe a specific behaviour for its instances, which are python objects . These objects of that class are created using the class as the blueprint . A metaclass in python is a way to describe a specific behaviour for its instances, which are python classes. A metaclass is the blueprint of the class itself, just like a class is the blueprint for instances of that class. The default metaclass is type, and all metaclasses must derive from type .
answered Jan 10
Every object and class in Python is either an instance of a class or an instance of a metaclass. Every class inherits from the built-in basic base class object
, and every class is an instance of the metaclass type
. Except for type
, type
is its metaclass and base class (don’t ask “how?”, it’s done using an implementation level hack). Just like how a class defines the behaviour of its object, a metaclass defines the behaviour of classes. The main purpose of metaclasses is to change the behaviour of classes as soon as they are created.
Although you’ve probably never explicitly used metaclasses, they’re littered everywhere if you were to look under the hood. For instance, if you’ve ever created an abstract class in Python using the ABC
module, you indirectly inherited the ABCmeta
class. Or, if you’re a backend developer who uses Django, you’ve indirectly used the ModelBase
metaclass through model.Model
.
Much like how you can dynamically create objects of a class using the syntax: class_name()
, you create a class using the syntax: type()
. Let’s illustrate this with an example:
class Dummy():
x = 12
def meme():
print("When you try to define constants \nPython: We don’t do that here.")
Dummy.meme()
When you try to define constants
Python: We don’t do that here.
The above syntax is equivalent to:
def nameless_func():
print("C++ – Can’t compare 'float' and 'int'. \nPython – Variable is variable.")
WierdDummy = type('WierdDummy',() ,{'x':12, 'meme': nameless_func})
WierdDummy.meme()
C++ – Can’t compare 'float' and 'int'.
Python – Variable is variable.
Here, ‘WierdDummy
‘ is the new class’s name, ()
is a tuple containing the base class(es) that can be empty. {'x':12, 'meme': nameless_func}
is a dictionary that stores all class attribute names and values. At first glance, this syntax seems obscure and useless, and it mostly is, but it can be extremely powerful for niche metaprogramming use cases. Imagine this scenario: You have four unrelated mixin classes with different functionalities, and you need to create all possible combinations of two. Now you could write all 6 new classes manually or dynamically create them with a few lines of code.
class A:
def show_a(self):
print("Class A")
class B:
def show_b(self):
print("Class B")
class C:
def show_c(self):
print("Class C")
class D:
def show_d(self):
print("Class D")
from itertools import combinations
for base_classes in combinations([A, B, C, D], 2):
new_class_name = "".join([c.__name__ for c in base_classes])
globals()[new_class_name] = type(new_class_name , base_classes,{})
obj = AB()
obj.show_a()
obj.show_b()
Class A
answered Jan 10