Metaclasses in Python

Translation of the article was prepared on the eve of the start of the course Python Developer. Basic “


Metaclasses are classes whose instances are themselves classes. Just as a “regular” class defines the behavior of instances of a class, a metaclass defines both the behavior of classes and the behavior of their instances.

Metaclasses are not supported by all object-oriented programming languages. The programming languages ​​that support them differ significantly in the way they are implemented. But Python has metaclasses.

Some programmers think of metaclasses in Python as “solutions waiting or looking for a problem.”

Metaclasses have many uses. Let’s highlight a few of them:

  • Logging and profiling;

  • Interface testing;

  • Registering classes at creation time;

  • Automatic creation of properties;

  • Proxy;

  • Automatic blocking / synchronization of resources.

Defining metaclasses

In general, metaclasses are defined just like any other class in Python, but they are classes that inherit from “type”. Another difference is that the metaclass is called automatically when the class statement using the metaclass ends. In other words, if the keyword metaclass is not passed after the base classes of the class header (however, there may not be base classes), then it will be called type() (those. _call_ type). On the other hand, if the keyword metaclass is used, the assigned class will be called instead of type

Let’s create a very simple metaclass. He can do nothing, except for displaying the contents of his arguments in the method_new_ and return the call result type._new_:

class LittleMeta(type):
    def __new__(cls, clsname, superclasses, attributedict):
        print("clsname: ", clsname)
        print("superclasses: ", superclasses)
        print("attributedict: ", attributedict)
        return type.__new__(cls, clsname, superclasses, attributedict)

Now let’s use the metaclass LittleMeta in the following example:

class S:
    pass


class A(S, metaclass=LittleMeta):
    pass


a = A()
clsname:  A
superclasses:  (<class '__main__.S'>,)
attributedict:  {'__module__': '__main__', '__qualname__': 'A'}

We see what was called LittleMeta._new_, but not type._new_

Let’s define a metaclass EssentialAnswerswhich can automatically include our method augmen_tanswer:


x = input("Do you need the answer? (y/n): ")
if x.lower() == "y":
    required = True
else:
    required = False

    
def the_answer(self, *args):              
        return 42

    
class EssentialAnswers(type):
    
    def __init__(cls, clsname, superclasses, attributedict):
        if required:
            cls.the_answer = the_answer
                           
    
class Philosopher1(metaclass=EssentialAnswers): 
    pass


class Philosopher2(metaclass=EssentialAnswers): 
    pass


class Philosopher3(metaclass=EssentialAnswers): 
    pass
    
    
plato = Philosopher1()
print(plato.the_answer())


kant = Philosopher2()
# let's see what Kant has to say :-)
print(kant.the_answer())
Do you need the answer? (y/n): y
42
42

In the chapter “Type and Class Relationship” we found out that after processing a class definition, Python calls:

type(classname, superclasses, attributes_dict)

But not when the metaclass was declared in the header. This is exactly what we did in our last example. Our classes Philosopher1, Philosopher2 and Philosopher3 were “attached” to the metaclass EssentialAnswers… And that’s why EssentialAnswers will be called instead type:

EssentialAnswer(classname, superclasses, attributes_dict)

To be precise, the following values ​​will be given to the call arguments:

EssentialAnswer('Philopsopher1', 
                (), 
                {'__module__': '__main__', '__qualname__': 'Philosopher1'})

Other classes Philosopher will behave similarly.

Creating singletons with metaclasses

Singleton is a design pattern that allows you to create just one instance of a class. It is used in cases where exactly one object is needed. The concept can be generalized, that is, we can limit the creation of class instances to a certain or fixed number. The term itself came to us from mathematics, where a singleton, also called a unit set, is used to refer to a set with only one element.

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton,                                      cls).__call__(*args, **kwargs)
        return cls._instances[cls]
    
    
class SingletonClass(metaclass=Singleton):
    pass


class RegularClass():
    pass


x = SingletonClass()
y = SingletonClass()
print(x == y)


x = RegularClass()
y = RegularClass()
print(x == y)
True
False

We can also create Singleton classes by inheriting from Singletonwhich can be defined as follows:

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance

    
class SingletonClass(Singleton):
    pass

class RegularClass():
    pass


x = SingletonClass()
y = SingletonClass()
print(x == y)


x = RegularClass()
y = RegularClass()
print(x == y)
True
False

Learn more about the course.


Read more:

  • Why you should start using FastAPI right now

  • When you shouldn’t use lists in Python

  • Introduction to Asynchronous Python Programming

Similar Posts

Leave a Reply