What Are Metaclasses in Python

metaclass 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.

Create Classes Dynamically 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 do 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 into 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 Organic class to the Fruit class:

class Organic:
    pass

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

Awesome!

Now you know how to create a class using the build-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 attribute to uppercase automatically. So that 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 classes 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 usefull.

When Are Metaclasses Useful in Python

Now you understand what is a metaclass in Python. More importantly, you now know how to alter the way classes are constructed in Python.

Writing custom metaclasses can be useful because this way you can enforce constraints on how classes are created.

For instance, you can write a metaclass that does not allow some attributes to be used at all. Or you could write a metaclass that disables inheritance.

You can affect anything about creating a class with a metaclass.

Writing a metaclass is useful when you are writing module/library code and you want the classes to follow a specific structure.

When a developer then uses this library, for example, to inherit from a specific class, you can ensure the class has a specific function to make it work.

Conclusion

In Python, everything is an object—even a class.

A metaclass is a “class of a class” in Python. It acts as a class factory. Whenever you use the keyword class, a metaclass creates a class object for your class definition behind the scenes.

You can also write custom metaclasses in Python. This makes it possible to lay some constraints on classes. For example, you can write a metaclass that prohibits using an attribute with a specific name.

It is very rare you have to write metaclasses in Python. In fact, for most of the developers (including myself), knowing how a metaclass works is not even necessary. But I think it is insightful to understand that aspect of Python too.

If you need to write a metaclass one day I wish it does not sound scary to you now that you learned how to do it. However, keep in mind this was just a basic introduction to metaclasses in Python. If you want to learn more details, please refer to the documentation.

Thanks for reading.

Happy coding!

Further Reading

50 Python Interview Questions

Share on facebook
Share on twitter
Share on linkedin

Leave a Comment

Your email address will not be published.