What Are Metaclasses in Python

In Python, a metaclass is a “class of a class”. It defines how a class itself is constructed.

Whenever you create a Python class, a metaclass is invoked under the hood. This metaclass turns the class into a Python object.

To truly understand what a metaclass is, you need to understand how classes work.

In this guide, you will learn:

  • What is a metaclass in Python?
  • How to write a metaclass.
  • When metaclasses can be useful.

Before jumping into metaprogramming, I want to mention that what you are about to learn is something you will likely never use. But it is insightful to learn this side of Python too to become a more professional Pythonista!

Classes in Python

Everything in Python is an object—even a class.

By far you have learned that a Python class is a blueprint for producing objects. But what you may not have thought before is that classes themselves are also valid Python objects.

When you use the keyword class, Python creates an object behind the scenes.

For instance:

class Fruit:
    pass

This piece of code creates a class object in memory.

Because a class is also an object, this means you can do anything you can do with any other Python object.

For example, you can

  • Store a class into a variable.
  • Add attributes to a class.
  • Pass a class as an argument into a function.
  • Create an independent copy of a class.

Let’s demonstrate each of these with a Fruit class:

# Define a class
class Fruit:
    pass

# Assign the class object into a variable
c = Fruit
print(c)

# Add attributes to the class
Fruit.age = 0
print(Fruit.age)

# Pass the class object as an argument
def display(class_obj):
    print(class_obj)

display(Fruit)

# Copy the class object
import copy

Fruit2 = copy.deepcopy(Fruit)
print(Fruit2)

This code—believe it or not— runs successfully. Here is what it prints into the console:

<class '__main__.Fruit'>
0
<class '__main__.Fruit'>
<class '__main__.Fruit'>

This demonstrates well how a class is also an object in Python.

By the way, because a class is also an object, this means you can define and return a class from a function in Python too.

For example:

def give():
    class Something:
        pass
    return Something

# Example call
s = give()
print(s)

Output:

<class '__main__.give.<locals>.Something'>

This is something that is not possible in many other programming languages.

Dynamic Class Creation in Python

So far you have learned that a class is also an object in Python.

This means you can create classes on the fly like you would create any other objects.

Use ‘type’ Class to Create a New Class Dynamically

In Python, you can check the type of an object with the built-in type function.

For instance:

print(type(10))
print(type("Hello"))

Output:

<class 'int'>
<class 'str'>

This is something you probably already knew.

But did you know you can define a class using type too?

However, when you do this, you do not actually use the type() function. Instead, you use the built-in type class. (It is a bad naming convention to call a function and a class with the same name by Python. But it has to do with the backward compatibility.)

To define a class using the type class, use the following syntax:

type(name, bases, attrs)

Where:

  • name—the name of the new class.
  • bases—a tuple of the parent classes.
  • attrs—a dictionary with attributes names and values.

To demonstrate, let’s first create a class in a regular way:

class Fruit:
    pass

Then, let’s use the type class to create the same Fruit class:

Fruit = type('Fruit', (), {})

Now you can create an instance of the Fruit class and print it:

banana = Fruit()
print(banana)

As a result, you get the following output in the console:

<__main__.Fruit object at 0x10735dba0>

This shows you that a new Fruit object was successfully created.

Pretty cool, right? You just learned a completely new way to define a class in Python.

But how about adding attributes to this new class?

Normally, you would add attributes to a class this way:

class Fruit:
    color = "Green"

But when you create a class using type, you need to define a dictionary that represents the attributes of the class.

For example, let’s add the color attribute to the Fruit class:

Fruit = type('Fruit', (), {'color': 'Green'})

Now you can check that it works by creating a Fruit object and printing it out:

banana = Fruit()
print(banana.color)

Output:

Green

It works!

Now that you know how to add attributes to the class, you also need to know how to add methods.

In a regular class, you can define a method inside the class this way:

class Fruit:
    color = "Green"

    def info(self):
        print(self.color)

Example call:

apple = Fruit()
apple.info()

Output:

Green

When using type, you need to separately define the methods. Then you need to pass them into the attributes dictionary the same way you can add variables to the class.

For example, let’s create an info() method for the Fruit class:

def info(self):
    print(self.color)

Fruit = type('Fruit', (), {'color': 'Green', 'info': info})

Example call:

apple = Fruit()
apple.info()

Output:

Green

This also works!

Sometimes you also want to inherit from a parent class. Regularly you would do it by passing the parent class into a set of parenthesis in the subclass:

class Organic:
    pass

class Fruit(Organic):
    pass

When creating a class with type, the parent class must be specified in the bases tuple.

For instance, let’s inherit the Organic class to the Fruit class:

class Organic:
    pass

Fruit = type('Fruit', (Organic, ), {})

Awesome!

Now you know how to create a class using the built-in type class.

But why all the hassle?

The whole point was to demonstrate it is possible to create classes dynamically in Python.

As it turns out, it is this very process that Python does behind the scenes whenever you use the class keyword.

With this information, you are now ready to jump into the metaclasses in Python.

Metaclasses in Python

Metaclasses are the objects that create classes. You can think of a metaclass as being a “class of a class”.

In the previous section, you learned how to use the built-in type class to create a new class.

Fruit = type('Fruit', (), {'color': 'Green'})

This type class is an example of a metaclass in Python.

Python uses type metaclass to create each and every class under the hood. So whenever you define a class in Python, the metaclass type is invoked behind the scenes.

Custom Metaclasses in Python

To truly understand how metaclass works in Python, let’s create a custom metaclass.

Remember, a metaclass is like a class factory. It is used to create classes behind the scenes.

Let’s create a custom metaclass called Meta:

class Meta(type):
    def __new__(self, class_name, bases, attrs):
        print(attrs)
        return type(class_name, bases, attrs)
  • This custom metaclass inherits the type metaclass because it does some useful operations when creating a class.
  • The __new__ method is the first method that gets called when a new object is created. It lets you change the way an object is created. In this example, we print the attributes before creating the class.
  • The last line return type(class_name, bases, attrs) uses the type metaclass to create a new class.

Now, let’s create a new class using the Meta class.

To do this, specify the optional metaclass parameter in the class as Meta. This replaces using the default metaclass type to use a custom metaclass:

class Fruit(metaclass=Meta):
    color = "Green"

If you run this piece of code, you can see the following in the console:

{'__module__': '__main__', '__qualname__': 'Fruit', 'color': 'Green'}

Think about it. You created a class Fruit without creating any Fruit objects, but it still ended up printing info to the console.

This happens because the code in the Meta class gets executed whenever you define a new class.

If you now have a look at the output, you can see it has the __qualname__ (the name of the class) set Fruit and the color attribute set Green—just like you wanted.

Now, Fruit is a regular Python class you can use to create objects. For example:

f = Fruit()
print(f.color)

Output:

Green

Now, let’s make a change to the Meta class. In other words, let’s change the way classes are constructed.

More specifically, let’s convert each class attributes to uppercase automatically. For example, fruit.color becomes fruit.COLOR under the hood.

To do this, you need to modify the __new__ method in the Meta class:

class Meta(type):
    def __new__(self, class_name, bases, attrs):

        modified_attrs = {}
        for name, value in attrs.items():
            if name.startswith("__"):
                modified_attrs[name] = value
            else:
                modified_attrs[name.upper()] = value

        return type(class_name, bases, modified_attrs)

Here we take the new class attributes and convert each attribute (that does not begin with a double underscore) to uppercase.

Now, let’s define a new Fruit class:

class Fruit(metaclass=Meta):
    color = "Green"

Now, let’s create a Fruit object and print its color:

f = Fruit()
print(f.color)

This time it results in the following error:

AttributeError: 'Fruit' object has no attribute 'color'

This happens because each class attribute now gets converted to uppercase. In other words, there is no color attribute in the Fruit class. Instead, it is called COLOR.

Let’s verify this by trying to access it:

f = Fruit()
print(f.COLOR)

Output:

Green

This succeeds. As you can see, using a metaclass you were able to alter the way a class gets constructed behind the scenes.

Now you understand what is a metaclass in Python and how to write one yourself. Finally, let’s take a look at when this is actually useful.

When Use Metaclasses in Python?

In Python, a metaclass is a way to alter the way classes are constructed.

By writing custom metaclasses, you can enforce constraints on class creation, such as disallowing certain attributes or disabling inheritance.

Metaclasses can be used to ensure classes in a module or library follow a specific structure, making it easier for developers to use them.

Conclusion

In Python, even a class is an object, and a metaclass is a class of a class that acts as a class factory. Metaclasses can be used to impose constraints on classes, such as prohibiting the use of a certain attribute.

However, writing metaclasses is rare, and not necessary for most developers. Understanding how metaclasses work can be insightful, but for more details, refer to the documentation.

Further Reading

50 Python Interview Questions