A Practical Guide to Metaprogramming in Python

Karishma Shukla - Jul 16 '23 - - Dev Community

What is metaprogramming?

Metaprogramming is a programming technique where a program can modify or generate code at runtime. It allows developers to write code that can analyze, modify, or create other code.

In other words, metaprogramming is a way of writing programs that manipulate programs.

Metaprogramming is supported in a lot languages including Python, JavaScript, Ruby, Clojure, Julia and Java (even though it is considered to be a static and verbose language!)

Relationship between structural and process concepts of metaprogramming. <br>

Fig: Relationship between structural and process concepts of metaprogramming. Image from Research Gate

Where is metaprogramming used?

Metaprogramming is useful for a lot of use cases. Some of them include

  • Frameworks and Libraries
  • Code Generation
  • Template Engines

Metaprogramming in Python

Metaprogramming in Python allows you to write code that can manipulate code itself, creating new code, modifying existing code, or analyzing code structures. Python provides several mechanisms for metaprogramming. Let's explore each of them with code examples:

1. Decorators

Decorators are functions that modify the behavior of other functions or classes. They wrap the target function or class and provide additional functionality. Decorators use the @ symbol and can be applied to functions or classes. Here's an example:

def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_decorator
def greet(name):
    return f"Hello, {name}!"

print(greet("Karishma"))
Enter fullscreen mode Exit fullscreen mode

Output

HELLO, KARISHMA!
Enter fullscreen mode Exit fullscreen mode

In the above example, the uppercase_decorator modifies the behavior of the greet function by converting its return value to uppercase.

2. Metaclasses

Metaclasses allow you to define the behavior of classes. They act as the blueprint for creating classes and can modify class creation and behavior. Here's an example:

class MetaClass(type):
    def __new__(cls, name, bases, attrs):
        uppercase_attrs = {key.upper(): value for key, value in attrs.items() if not key.startswith('__')}
        return super().__new__(cls, name, bases, uppercase_attrs)

class MyClass(metaclass=MetaClass):
    message = "Hello, World!"

print(MyClass.MESSAGE)
Enter fullscreen mode Exit fullscreen mode

Output

Hello, World!
Enter fullscreen mode Exit fullscreen mode

In the above example, the MetaClass metaclass modifies the attributes of the MyClass class by converting them to uppercase.

3. Function and Class Decorators

Besides using decorators with the @ symbol, you can also apply decorators using the function or class syntax. Here's an example:

class CubeCalculator:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):

        # before function
        result = self.function(*args, **kwargs)

        # after function
        return result

# adding class decorator to the function
@CubeCalculator
def get_cube(n):
    print("Input number:", n)
    return n * n * n

print("Cube of number:", get_cube(100))
Enter fullscreen mode Exit fullscreen mode

Output

Input number: 100
Cube of number: 1000000
Enter fullscreen mode Exit fullscreen mode

In the above example, we define a class decorator CubeCalculator that wraps the function get_cube to perform additional actions before and after its execution, and then applies the decorator to the get_cube function. When get_cube is called with an input number, it prints the input number, calculates its cube, and returns the result.

4. Dynamic Code Generation

Python allows you to generate code dynamically using techniques such as eval() or exec(). This can be useful for generating code based on certain conditions or dynamically creating functions or classes. Here's an example:

name = "Karishma"
age = 2

code = f'def greet():\n    print("Name: {name}")\n    print("Age: {age}")'

exec(code)
greet()
Enter fullscreen mode Exit fullscreen mode

Output

Name: Karishma
Age: 2
Enter fullscreen mode Exit fullscreen mode

In the above example, the code string is dynamically generated and executed using exec() to define the greet function.

Most common in-built keywords and functions for metaprogramming in Python

In Python, the following keywords and concepts are commonly associated with metaprogramming and provide the foundation for metaprogramming:

  1. getattr(), dir(), hasattr(): These functions allow you to dynamically get and set attributes of an object at runtime. The built-in inspect module is responsible for an important concept of metaprogramming called introspection in which the program simply looks at and reports on itself.

  2. exec(), eval() and compile(): These functions enable the execution of dynamically generated code strings or evaluation of expressions.

  3. __getattr__() and __setattr__(): These special methods can be defined in classes to handle attribute access and assignment dynamically.

  4. __metaclass__: This special attribute can be used in a class definition to specify a metaclass for that class.

Conclusion

These are some of the ways to achieve metaprogramming in Python. Each technique provides flexibility in manipulating code structures and behavior, enabling you to create more dynamic and flexible applications.


Find me on GitHub, Twitter

. . . . . . . .