What are metaclasses in Python?

Asked : Nov 17

Viewed : 59 times

In Python, what are metaclasses and what do we use them for?

oop python metaclass python-class python-datamodel 

Nov 17

5 Answers

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 tl;dr version

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.

Everything in Python is an Object

class TestClass():
    pass

my_test_class = TestClass()
print(my_test_class)
<__main__.TestClass object at 0x7f6fcc6bf908>

Python Classes can be Created Dynamically

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


Python Metaclasses

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


Login and Submit Your Answer
Browse other questions tagged  oop  python  metaclass  python-class  python-datamodel or ask your own question.