Metaprogramming - CameronAuler/python-devops GitHub Wiki

Metaprogramming is a technique where code modifies itself or manipulates classes dynamically at runtime. Python supports metaprogramming through metaclasses, dynamic class creation, and the type() function.

Table of Contents

Metaclasses

A metaclass is a class that defines the behavior of other classes. Normally, Python classes are created using class, but metaclasses allow modifying class creation dynamically. Classes in Python are instances of a metaclass (default is type).

How Classes Work

In the example below, Foo is a class, but it is also an instance of type, which is the default metaclass. A metaclass controls how classes are created, just as classes control how objects are created.

class Foo:
    pass

print(type(Foo))  # <class 'type'> (Foo is an instance of 'type')
# Output:
<class 'type'>

Creating a Custom Metaclass

Metaclasses define __new__() or __init__() to modify class creation. Meta is a metaclass (inherits from type). When MyClass is defined, Meta.__new__() is called, modifying its creation.

class Meta(type):  # Inherit from 'type' to create a metaclass
    def __new__(cls, name, bases, class_dict):
        print(f"Creating class: {name}")
        return super().__new__(cls, name, bases, class_dict)

class MyClass(metaclass=Meta):  # Assign Meta as metaclass
    pass
# Output:
Creating class: MyClass

Dynamic Classes

Creating Classes at Runtime

Classes can be created dynamically using type(), which is Python’s built-in metaclass. type(name, bases, dict) creates a class dynamically.

  • name: Class name (MyDynamicClass).
  • bases: Parent classes (() means no parent).
  • dict: Class attributes ({"x": 42} adds an attribute x).
MyDynamicClass = type("MyDynamicClass", (), {"x": 42})

print(MyDynamicClass)  # <class '__main__.MyDynamicClass'>
print(MyDynamicClass().x)  # 42
# Output:
<class '__main__.MyDynamicClass'>
42

Adding Methods Dynamically

Functions can be added dynamically when creating classes. This is mainly used for generating classes at runtime, useful in ORMs, frameworks, and dynamic APIs.

def hello(self):
    return "Hello from dynamic class!"

DynamicClass = type("DynamicClass", (), {"greet": hello})

obj = DynamicClass()
print(obj.greet())  # Calls the dynamically added method
# Output:
Hello from dynamic class!

Using type() for Introspection & Modification

The type() function is used for checking the type of an object and Creating new classes dynamically (as seen above).

Checking Object Type

This is mainly used for type checking at runtime.

print(type(42))  # <class 'int'>
print(type("Hello"))  # <class 'str'>
print(type([]))  # <class 'list'>
# Output:
<class 'int'>
<class 'str'>
<class 'list'>

Modifying an Existing Class

New attributes can be added dynamically using type(). It is mainly used for monkey patching, testing, debugging, and dynamic behavior changes.

class Person:
    pass

Person.age = 30  # Dynamically add an attribute
print(Person.age)  # 30
# Output:
30

Combining Metaclasses & type()

We can use type() inside a metaclass to generate classes dynamically. Meta.__new__() modifies the class before it’s created. dynamic_attribute is added dynamically during class creation.

class Meta(type):
    def __new__(cls, name, bases, class_dict):
        print(f"Creating class dynamically: {name}")
        class_dict["dynamic_attribute"] = 100  # Add an attribute
        return type.__new__(cls, name, bases, class_dict)

class DynamicClass(metaclass=Meta):
    pass

print(DynamicClass.dynamic_attribute)  # 100
# Output:
Creating class dynamically: DynamicClass
100