All writing

Python: Decorators & Metaclasses

A deep look at two handy Python features — decorators for adding behaviour to functions without changing them, and metaclasses for customising how classes themselves are built.

Python decorators and metaclasses are handy features of the Python programming language. They are used to add functionality to classes and functions, and to modify the behavior of the language itself. In this article, I will discuss these features in depth, and provide examples to illustrate their usage.

A decorator is a function that takes another function as input, and returns a new function that wraps the original function. Decorators are used to modify the behavior of a function, without changing its code. The @ symbol is used to apply a decorator to a function. For example:

@decorator
def function(arg1, arg2):
    return arg1 + arg2
    # function code

The decorator function can be defined as follows:

def decorator(func):
    def wrapper(*args, **kwargs):
        # code to be executed before the original function
        result = func(*args, **kwargs)
        # code to be executed after the original function
        return result
    return wrapper

In this example, the decorator function takes a function as input, and returns a new function that wraps the original function. The new function executes some code before and after the original function, and then returns the result of the original function.

Assume we want to log the time and date that a function is called. We can achieve this with decorators, as shown below:

from datetime import datetime

def log_datetime(func):
    def wrapper():
        print(f'Start time: {datetime.today().strftime("%Y-%m-%d %H:%M:%S.%f")}')
        func()
        print(f'End time: {datetime.today().strftime("%Y-%m-%d %H:%M:%S.%f")}')
    return wrapper

@log_datetime
def my_function():
    print(f'{"-"*10} Function Called {"-"*10}')

my_function()

Which gives the following output:

Start time: 2022-04-23 19:16:07.332242
---------- Function Called ----------
End time: 2022-04-23 19:16:07.332282

A metaclass, on the other hand, is a class that defines the behavior of other classes. In Python, classes are also objects, which means that they can be created and modified at runtime. Metaclasses provide a way to customize the creation of classes, by adding or modifying their attributes and methods.

To create a metaclass, you need to define a new class that inherits from type, and override the __new__() method. The __new__() method is called when a new class is created, and it returns the new class object. You can modify the attributes and methods of the new class object before returning it.

Here is an example of a metaclass that adds a new attribute to all classes created with it:

class MyMeta(type):
    def __new__(cls, name, bases, attrs):
        attrs['new_attr'] = 'new value'
        return super().__new__(cls, name, bases, attrs)

In this example, the MyMeta class inherits from type, which means that it is a metaclass. The __new__() method is defined to add a new attribute called 'new_attr' to the attributes of the new class object. The super() function is used to call the __new__() method of the parent class (type), and return the new class object.

To use this metaclass, you can define a new class that inherits from object, and set its metaclass to MyMeta:

class MyClass(object, metaclass=MyMeta):
    pass

In this example, the MyClass class inherits from object, and its metaclass is set to MyMeta. When the MyClass class is created, the __new__() method of MyMeta is called, and the 'new_attr' attribute is added to its attributes.

In general, metaclasses are useful but rarely utilized, because the functionality they give is easily obtained through inheritance or a workaround employing decorators. In any case, it's good to know they exist and can help you write cleaner code.

Python decorators and metaclasses are powerful features that allow you to modify the behavior of classes and functions, and to customize the creation of classes. Decorators are used to add functionality to functions without changing their code, while metaclasses are used to define the behavior of other classes. By using decorators and metaclasses, you can create more flexible and customizable Python code.

I thought these were some interesting tools for writing more consistent and cleaner Python code, and wanted to share my opinions on them. For those interested in learning more about decorators and metaclasses, here are some resources: