Metaclasses

Python provides a lot of features, some of which are directly visible to us, such as list comprehensions, dynamic type evaluation, and so on, and some of them not so directly. In Python, a lot of things can be considered magic, happening behind the scenes. One of them is the concept of metaclasses.

In Python, everything is an object, be it a method or a class. Even inside Python, classes are considered to be first-class objects that can be passed on to methods, assigned to variables, and so on.

But, as the concept of OOP states, every object denotes an instance of a class. So, if our classes are objects, then they should also be instances of some class. So, which class is that? The answer to this question is the type class. Every class in Python is an instance of the type class.

This can be verified quite easily, as shown in the following snippet:

class A:
def __init__(self):
print("Hello there from class A")

>>>isinstance(A, type)
True

These classes, whose object is a class, are known as metaclasses.

In Python, we don't often use metaclasses directly, because most of the time the problems we are trying to solve with the help of metaclasses can usually be solved through the use of some other simple solutions. But the metaclasses do provide us with a lot of power to how we create our classes. Let's first take a look at how we can create our own metaclasses by designing a LoggerMeta class, which will enforce the instance class to provide a valid handler method for different log methods prefixed by HANDLER_:

class LoggerMeta(type):
def __init__(cls, name, base, dct):
for k in dct.keys():
if k.startswith('HANDLER_'):
if not callable(dct[k]):
raise AttributeError("{} is not callable".format(k))
super().__init__(name, base, dct)


def error_handler():
print("error")
def warning_handler():
print("warning")


class Log(metaclass=LoggerMeta):
HANDLER_ERROR = error_handler
HANDLER_WARN = warning_handler
HANDLER_INFO = 'info_handler'

def __init__(self):
print(“Logger class”)

In this example, we have defined a metaclass named LoggerMeta by inheriting from the type class. (For defining any metaclass, we need to either inherit from the type class or any other metaclass. The concept of inheritance is applicable even during the metaclass creation.) Once we have declared our metaclass, we provide a definition for the __init__ magic method in the metaclass. The __init__ magic method of the metaclass takes in the class object, the name of the new class to be created, a list of base classes the new class will derive from, and a dictionary containing the properties of the new class that is used to initialize the new class.

Inside the __init__ method, we have provided an implementation for verifying if the class properties whose name starts with HANDLER_ have a valid handler assigned to them or not. In case the handler assigned to the property is not callable, we raise an AttributeError and prevent the creation of the class. And at the end of the __init__ method, we return the call results of a base class __init__ method.

In the next example, we create two simple methods that will act as our handlers for dealing with error type messages and warning type messages.

Moving on in the example, we define a class log whose metaclass is LoggerMeta. This class contains a few properties, such as HANDLER_ERROR, HANDLER_WARN, HANDLER_INFO and the magic method __init__.

Now, let's see what happens if we try to execute the provided example:

python3 metaclass_example.py
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __init__
AttributeError: HANDLER_INFO is not callable

As we can see from the output, as soon as the definition of class log was parsed by the interpreter to create the class, the metaclass __init__ method was called, which validated the properties of the class and raised an AttributeError.

The metaclasses in Python provide us with a lot of power at our disposal and enable us to do a lot of things magically, for example, generating class properties based on the name of the methods, and keeping track of how many instances of a class have been initialized.

With all the things we have learned about OOP and metaclasses in Python, let's now move on to using them to implement some of the design patterns in Python, and learning about how to decide upon the choice of the design pattern to use.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.72.74