Book Image

Metaprogramming with Python

By : Sulekha AloorRavi
Book Image

Metaprogramming with Python

By: Sulekha AloorRavi

Overview of this book

Effective and reusable code makes your application development process seamless and easily maintainable. With Python, you will have access to advanced metaprogramming features that you can use to build high-performing applications. The book starts by introducing you to the need and applications of metaprogramming, before navigating the fundamentals of object-oriented programming. Next, you will learn about simple decorators, work with metaclasses, and later focus on introspection and reflection. You’ll also delve into generics and typing before defining templates for algorithms. As you progress, you will understand your code using abstract syntax trees and explore method resolution order. This Python book also shows you how to create your own dynamic objects before structuring the objects through design patterns. Finally, you will learn simple code-generation techniques along with discovering best practices and eventually building your own applications. By the end of this learning journey, you’ll have acquired the skills and confidence you need to design and build reusable high-performing applications that can solve real-world problems.
Table of Contents (21 chapters)
1
Part 1: Fundamentals – Introduction to Object-Oriented Python and Metaprogramming
4
Part 2: Deep Dive – Building Blocks of Metaprogramming I
11
Part 3: Deep Dive – Building Blocks of Metaprogramming II

An overview of metaprogramming

Metaprogramming is a concept widely heard of in other programming languages such as C++, Java, .NET, and Ruby but not so widely heard of in Python. Python is a programming language that is easy to learn for beginners to programming and efficient to implement for advanced programmers. Therefore, it has an additional advantage in improving efficiency and optimization while developing high-performance applications when techniques such as metaprogramming are blended with the process of application development.

In this book, we will deep dive into the concepts of metaprogramming using Python 3.

The term meta, as the name suggests, is a process that references itself or its the high-level information. In the context of programming, metaprogramming also describes the similar concept of a program referencing itself or a program object referencing itself. A program referencing itself or its entity gives data on the program or the programming entity that can be used at various levels to perform activities, such as transformations or manipulations, in a programming language.

To understand the term meta, let’s consider the term metadata. As an example, let’s look at a Python DataFrame. For those who are not familiar with the term DataFrame, we can use the term table. The one shown in the following screenshot is called Employee Data:

Figure 1.1 – Employee Data table

Figure 1.1 – Employee Data table

This Employee Data table consists of employee information such as the name of the employee, employee ID, qualification, experience, salary, and so on.

All of this information are attributes of single or multiple employees, and it is the data of employees in an organization. So, what will the metadata be? The metadata is the data of how employee data is stored in the Employee Data table.

The metadata for the Employee Data table defines how each column and its values are stored in the table. For example, in the following screenshot, we can see metadata where Name is stored as a string with a length of 64 characters, while Salary is stored as a Float with a length of 12 digits:

Figure 1.2 – Metadata representation for the Employee Data table

Figure 1.2 – Metadata representation for the Employee Data table

Accessing, modifying, transforming, and updating the Employee Data table using information such as the name or ID of an employee is data manipulation, while accessing, modifying, transforming, and updating the data type or size of the column name or employee ID or salary, is metadata manipulation.

With this understanding, let’s look at an example of metaprogramming.

Metaprogramming – a practical introduction

Any programming language that can be used to write code to perform actions consists of a basic unit or piece of code that can be written to perform an action. This is known as a function.

If we have two numbers stored in two variables, a and b, to perform an add action, you can simply add those two numbers by writing a function, as shown in the following code block:

def add(a,b):    
    c = a + b    
    return c  

Now, if we execute this code, it can go through different scenarios, depending on the input data provided to the add function. Let’s take a close look at each of them.

Scenario 1

Running the add function with two integers would result in two numbers being added together, as follows:

add(1,3)  
4

Scenario 2

Running the add function with two strings would result in the concatenation of two words, as follows:

add('meta','program')   
metaprogram

Scenario 3

Let’s take a look at running the add function with one string and one integer:

add('meta',1)  

The preceding code would result in the following error:

Figure 1.3 – TypeError

Figure 1.3 – TypeError

Let’s examine this error in detail.

The error in the preceding code snippet denotes a TypeError, which was caused by an attempt to add a meta string with an integer of 1. The question that may occur to you is, can we resolve this error using metaprogramming?

The add function in this example denotes a piece of code or program, similar to how the Employee Data table in Figure 1.1 denotes data. In the same line, can we identify the metadata of the add function and use it to resolve the TypeError object returned by the following code:

add('meta',1)  

Next, we will look at a practical example of metaprogramming. We will be making use of the metadata of the add function to understand this concept.

Metadata of the add function

A function in any programming language is written to perform a set of operations on the input variables; it will return the results as a consequence of the operations performed on them. In this section, we will look at a simple example of a function that adds two variables. This will help us understand that metaprogramming can be applied to functions and manipulate the behavior of the function without modifying the algorithm of the function. We will be adding these two variables by writing an add function. To change the results of the add function, we will be manipulating the metadata of its two input variables, thus getting different results each time a different type of input variable is provided to execute the function. Just like we can manipulate what a function should do by writing lines of code to perform various operations, we can also manipulate the function itself by programming its metadata and setting restrictions on what it should and shouldn’t do. Just like a dataset, DataFrame, or table has data and metadata, a program or a function in Python 3 also has data and metadata. In this example, we will be manipulating the actions that are performed by the add function by restricting its behavior – not based on the input data provided to the function but on the type of input data provided to the add function instead. Take a look at the following screenshot:

Figure 1.4 – Examining the data and metadata of the add function

Figure 1.4 – Examining the data and metadata of the add function

The following code helps us identify the metadata for each data item in the add function:

def add(a,b):    
    c = a +  b    
    print ("Metadata of add", type(add))    
    print ("Metadata of a", type(a))    
    print ("Metadata of b", type(b))    
    print ("Metadata of c", type(c))   

A function call to the preceding function will now return the metadata of the add function instead of its result. Now, let’s call the add method with an integer as input:

add(1,3)  

We’ll get the following output:

Metadata of add <class 'function'>
Metadata of a <class 'int'>
Metadata of b <class 'int'>
Metadata of c <class 'int'>

Similarly, we can also check the addition of strings, as follows:

add('test','string')

We’ll get the following output:

Metadata of add <class 'function'>
Metadata of a <class 'str'>
Metadata of b <class 'str'>
Metadata of c <class 'str'>

Python 3 allows us to use the metadata of the code to manipulate it so that it deviates from its actual behavior. This will also provide customized solutions for the problems we are trying to solve.

In the preceding example, we used the type function, a method in Python that returns the class or data type that any object or variable belongs to.

From the preceding output, it is evident that the a and b variables we passed to the add function belong to the integer data type, and its result, c, is an integer too. The add function itself is of the function class/type.

Resolving type errors using metaprogramming

There are many variations on how we can resolve the type error from the add function we saw in the previous section using metaprogramming. We will look at this in this section.

Scenario 1

The following meta-program handles the error and allows the add function to add two strings or two integers. It also suggests that the user enters the input data with the right data types:

def add(a,b):
    if (type(a) is str and type(b) is int) or\
        (type(a) is int and type(b) is str):
        return "Please enter both input values as integers or\
          string"
    else:
        c = a + b
        return c  

In the function definition of add, we have added two conditions – one to check if the type of a is a string and the type of b is an int, or if the type of a is an int and the type of b is a string. We are checking the combination of these input variables to handle the type mismatch error and directing the users to provide the right data type for input variables.

The following table shows the various combinations of input variable data types and their corresponding output or results based on the conditions set on the metadata of the add function, based on Scenario 1:

Figure 1.5 – Scenario 1 metadata combinations

Figure 1.5 – Scenario 1 metadata combinations

The following code executes the add function to reinforce the input-output combinations explained in Figure 1.5:

add(1,3)  
4
add('meta','program')  
metaprogram
add('meta',1)  
'Please enter both input values as integers or string'
add(1,'meta')  
'Please enter both input values as integers or string'

Scenario 2

The following meta-program resolves the type mismatch error by converting the mismatching data types into string variables and performing a string concatenation. It is only logical to concatenate a string and an integer using a + operator as we cannot perform arithmetic addition on these two different data types. Take a look at the following program:

def add(a,b):
    if type(a) is int and type(b) is int:
        c = a +  b
        return c
    elif type(a) is str and type(b) is int or\
          type(a) is int and type(b) is str or \
          type(a) is str and type(b) is str:
        c = str(a) + str(b)
        return c
    else:
        print("Please enter string or integer")

Here, no matter what input we provide for the a and b variables, they both get converted into string variables and are then concatenated using +, whereas if both the input variables are integers, they get added using arithmetic addition.

The following table shows the various combinations of input variable data types and their corresponding output or results based on the conditions set on the metadata of the add function based on Scenario 2:

Figure 1.6 – Scenario 2 metadata combinations

Figure 1.6 – Scenario 2 metadata combinations

Executing the following code provides the combinations of output values we saw in the preceding table:

add(1343,35789)  
37132
add('Meta',' Programming')  
'MetaProgramming'
add('meta',157676)  
'meta157676'
add(65081, 'meta')  
'65081meta'
add(True, 'meta')
Please enter string or integer

Scenario 3

Now, let’s go a step further and restrict the nature of the add function itself to ensure it only performs arithmetic addition and doesn’t accept any other data types or combinations of data types.

In the following code block, we have added another condition to perform a data type check on floating-point values, along with data type checks for the string and integer input values.

This function only accepts numeric values as input and will return a message directing users to input numbers so that only arithmetic addition is performed. Let’s look at the code:

def add(a,b):
    if type(a) is int and type(b) is int or\
       type(a) is float and type(b) is float or\
       type(a) is int and type(b) is float or\
       type(a) is float and type(b) is int:
        c = a +  b
        return c
    else:
        return 'Please input numbers'

The following table shows the various combinations of input variable data types and their corresponding output or results based on the conditions set on the metadata of the add function based on Scenario 3:

Figure 1.7 – Scenario 3 metadata combinations

Figure 1.7 – Scenario 3 metadata combinations

Executing the following code provides the combination of output values shown in Figure 1.7, including the addition of floating-point values:

add(15443,675683)  
691126
add(54381,3.7876)  
54384.7876
add(6.7754,543.76)  
550.5354
add(79894,0.6568)  
79894.6568
add('meta',14684)  
'Please input numbers'
add(6576,'meta')  
'Please input numbers'
add('meta','program')  
'Please input numbers'

These are some of the approaches that can be applied to perform simple metaprogramming on a function. However, these are not the only solutions that solve type errors or manipulate a function. There is more than one way or approach to implementing solutions using metaprogramming.