https://medium.com/better-programming/meta-programming-in-python-7fb94c8c7152

A short tutorial on decorators and meta-classes

Recently, I encountered a very fascinating concept which is meta-programming in Python. I would like to share my findings on this topic in this article. I hope that it may help you to wrap your head around this because they say it is a tough nut to crack.

So, in one line: “Meta-programming is an act of writing code that manipulates code.”

Wait, what? Yes, you read it right. Code that manipulates code. Doesn’t it sound fascinating and powerful? Well, actually it is.

In the context of Python, meta-programming can be stated as: “Meta-programming is an act of building functions and classes that can manipulate code by modifying, wrapping existing code, or generating code.”

Meta-programming in Python can be achieved with:

Let’s get familiar with them one-by-one.

Decorators

A decorator is a way of adding new functionality to an existing function without modifying its original structure.

For instance, we have these three functions:

def add(x, y):
    return x + y    

def sub(x, y):
    return x - y
    
def mul(x, y):
    return x * y

Now we need to print the function name and parameter values when the function gets called. This should be applicable to all three functions above.

The native way is to add print/log statements to all three functions. But this sounds like very repetitive work and we’d also need to modify each function body.

def add(x, y):
    print("add is called with parameter {0},{1}".format(x,y))
    return x + y    

def sub(x, y):
    print("sub is called with parameter {0},{1}".format(x,y))
    return x - y
    
def mul(x, y):
    print("mul is called with parameter {0},{1}".format(x,y))
    return x * y    

print(add(5,3))
print(sub(5,3))
print(mul(5,3))

*********************** output *********************

add is called with parameter 5, 3
8
sub is called with parameter 5, 3
2
mul is called with parameter 5, 3
15

Can we do better? Of course we can, because by the grace of God, we are programmers and programmers are intelligent. We can achieve this by writing a decorator function and by not modifying any of the existing function body.

def my_decorator(func):
    def wrapper_function(*args):
        print("{0} is called with parameter {1}".format(func.__name__, args))
        return func(*args)
    return wrapper_function

@my_decorator
def add(x, y):
    return x + y
    
@my_decorator
def sub(x, y):
    return x - y

@my_decorator    
def mul(x, y):
    return x * y 

*********************** output *********************

add is called with parameter (5, 3)
8
sub is called with parameter (5, 3)
2
mul is called with parameter (5, 3)
15

Bingo! In the above code snippet, my_decorator is a decorator function. We decorate all three functions with @my_decorator and we have not touched the existing function body to add this print functionality.