ModifyCode Module#

Overview#

The modify_code module provides utilities for programmatically modifying Python source code. The ModifyCode class offers static methods for:

  • Adding decorators to functions

  • Adding import statements

  • Adding random seeds for reproducibility

  • Replacing division with protected division

  • Renaming functions

  • Analyzing function usage in code

This module uses Python’s Abstract Syntax Tree (AST) for robust code manipulation.

ModifyCode Class#

class llm4ad.base.modify_code.ModifyCode#

A utility class with static methods for modifying Python source code.

All methods are class methods and can be called directly without instantiation.

Methods#

llm4ad.base.modify_code.add_decorator(cls, program: str, function_name: str, decorator_name: str | List[str], decorator_args: List[str | Tuple[str, Any]] = None) str#

Adds a decorator to a specified function in the program.

Parameters:
  • program (str) – The program code as a string.

  • function_name (str) – The name of the function to decorate.

  • decorator_name (str | List[str]) – The name of the decorator. Can be a string (e.g., ‘numba.jit’, ‘torch.jit.script’) or a list of strings (e.g., [‘numba’, ‘jit’]).

  • decorator_args (List[str | Tuple[str, Any]] | None) – Optional arguments for the decorator as a list. Can include positional arguments (as strings) or keyword arguments (as tuples of (name, value)). Default is None.

Returns:

The modified program string with the decorator added.

Return type:

str

Example 1 - Simple Decorator:

program = '''
def f():
    return 0'''

result = ModifyCode.add_decorator(program, 'f', 'torch.jit.script')
# Result:
# @torch.jit.script()
# def f():
#     return 0

Example 2 - Decorator with kwargs:

program = '''
def f():
    return 0'''

result = ModifyCode.add_decorator(program, 'f', ['numba', 'jit'], [('nopython', True)])
# Result:
# @numba.jit(nopython=True)
# def f():
#     return 0

Example 3 - Complex Decorator:

program = '''
def f():
    return 0'''

result = ModifyCode.add_decorator(program, 'f', 'a.b.c.d', [1, True, ('e', 'all'), ('f', True)])
# Result:
# @a.b.c.d(1, True, e='all', f=True)
# def f():
#     return 0
llm4ad.base.modify_code.add_import_package_statement(cls, program: str, package_name: str, as_name: str | None = None, *, check_imported: bool = True) str#

Adds an import statement to the program.

Parameters:
  • program (str) – The program code as a string.

  • package_name (str) – The name of the package to import.

  • as_name (str | None) – Optional alias for the imported package (e.g., ‘np’ for ‘numpy’). Default is None.

  • check_imported (bool) – If True, checks if the package is already imported and returns the original program if so. Default is True.

Returns:

The modified program string with the import added.

Return type:

str

Example:

program = '''
def f():
    return np.array([1, 2, 3])
'''

result = ModifyCode.add_import_package_statement(program, 'numpy', 'np')
# Result:
# import numpy as np
#
# def f():
#     return np.array([1, 2, 3])
llm4ad.base.modify_code.add_numpy_random_seed_to_func(cls, program: str, func_name: str, seed: int = 2024) str#

Adds a numpy random seed assignment at the beginning of a function.

Parameters:
  • program (str) – The program code as a string.

  • func_name (str) – The name of the function to add the seed to.

  • seed (int) – The random seed value. Default is 2024.

Returns:

The modified program string with the seed added.

Return type:

str

Example:

program = '''
def compute():
    a = np.random.random()
    return a * 2
'''

result = ModifyCode.add_numpy_random_seed_to_func(program, 'compute', 42)
# Result:
# def compute():
#     np.random.seed(42)
#     a = np.random.random()
#     return a * 2
llm4ad.base.modify_code.replace_div_with_protected_div(cls, program: str, delta: float = 1e-5, numba_accelerate: bool = False, return_div_func_name: bool = False) str | Tuple[str, str]#

Replaces division operations with a protected division function that adds a small delta to the denominator.

Parameters:
  • program (str) – The program code as a string.

  • delta (float) – The delta value to add to the denominator. Default is 1e-5.

  • numba_accelerate (bool) – If True, adds the numba jit decorator to the protected division function. Default is False.

  • return_div_func_name (bool) – If True, returns a tuple of (modified_program, function_name). Default is False.

Returns:

The modified program string with protected division, or a tuple if return_div_func_name is True.

Return type:

str | Tuple[str, str]

Example:

program = '''
def compute(a, b):
    return a / b
'''

result = ModifyCode.replace_div_with_protected_div(program, delta=1e-8)
# Result:
# def compute(a, b):
#     return _protected_div(a, b)
#
#
# def _protected_div(x, y, delta=1e-08):
#     return x / (y + delta)
llm4ad.base.modify_code.add_np_random_seed_below_numpy_import(cls, program: str, seed: int = 2024) str#

Adds numpy import (if needed) and inserts np.random.seed() call below the import statement.

Parameters:
  • program (str) – The program code as a string.

  • seed (int) – The random seed value. Default is 2024.

Returns:

The modified program string with the import and seed.

Return type:

str

Example:

program = '''
import numpy as np

def f():
    return np.random.random()
'''

result = ModifyCode.add_np_random_seed_below_numpy_import(program, 123)
# Result:
# import numpy as np
# np.random.seed(123)
#
# def f():
#     return np.random.random()
llm4ad.base.modify_code.add_numba_decorator(cls, program: str, function_name: str | List[str]) str#

Adds the @numba.jit(nopython=True) decorator to one or more functions.

Parameters:
  • program (str) – The program code as a string.

  • function_name (str | List[str]) – The name of the function to decorate, or a list of function names.

Returns:

The modified program string with the numba decorator added.

Return type:

str

Example:

program = '''
import numba

def func(a: np.ndarray):
    return a * 2
'''

result = ModifyCode.add_numba_decorator(program, 'func')
# Result:
# import numba
#
# @numba.jit(nopython=True)
# def func(a: np.ndarray):
#     return a * 2
Note:

Not all numpy functions support numba acceleration (e.g., np.piecewise).

llm4ad.base.modify_code.rename_function(cls, code: str, source_name: str, target_name: str) str#

Renames function calls from source_name to target_name.

Parameters:
  • code (str) – The program code as a string.

  • source_name (str) – The original function name to replace.

  • target_name (str) – The new function name.

Returns:

The modified program string with renamed function calls.

Return type:

str

Example:

code = '''
def old_func(x):
    return x + 1

result = old_func(5)
'''

result = ModifyCode.rename_function(code, 'old_func', 'new_func')
# Result:
# def new_func(x):
#     return x + 1
#
# result = new_func(5)
llm4ad.base.modify_code.get_functions_name(cls, code: str) MutableSet[str]#

Returns the set of all function names that are called in the code.

Parameters:

code (str) – The program code as a string.

Returns:

A set of function names found in the code.

Return type:

MutableSet[str]

Example:

code = '''
import numpy as np

def my_func(x):
    return np.sum(x) + len(x)

result = my_func(arr)
another = len(result)
'''

functions = ModifyCode.get_functions_name(code)
print(functions)
# Output: {'np', 'sum', 'len', 'my_func'}
llm4ad.base.modify_code.yield_decorated(cls, code: str, module: str, name: str) Iterator[str]#

Yields names of functions decorated with a specific decorator.

Parameters:
  • code (str) – The program code as a string.

  • module (str) – The module name in the decorator (e.g., ‘numba’).

  • name (str) – The decorator name (e.g., ‘jit’).

Returns:

An iterator of function names that have the specified decorator.

Return type:

Iterator[str]

Example:

code = '''
import numba
import torch

@numba.jit
def fast_func(x):
    return x * 2

@torch.jit.script
def scripted_func(x):
    return x + 1

def normal_func(x):
    return x - 1
'''

decorated = list(ModifyCode.yield_decorated(code, 'numba', 'jit'))
print(decorated)
# Output: ['fast_func']

Usage Examples#

Example: Preparing Code for Evaluation#

from llm4ad.base.modify_code import ModifyCode

# Original candidate code
code = '''
import numpy as np

def objective(x):
    return np.sum(x ** 2) / len(x)
'''

# Step 1: Add numba decorator for acceleration
code = ModifyCode.add_numba_decorator(code, 'objective')

# Step 2: Replace division with protected division
code = ModifyCode.replace_div_with_protected_div(code, delta=1e-8, numba_accelerate=True)

# Step 3: Add random seed for reproducibility
code = ModifyCode.add_numpy_random_seed_to_func(code, 'objective', seed=42)

print(code)
# Output:
# import numpy as np
# import numba
#
# @numba.jit(nopython=True)
# def objective(x):
#     np.random.seed(42)
#     return _protected_div(np.sum(x ** 2), len(x))
#
#
# def _protected_div(x, y, delta=1e-08):
#     return x / (y + delta)

Example: Complete Code Preparation Pipeline#

from llm4ad.base.modify_code import ModifyCode

def prepare_for_evaluation(code: str, func_name: str, seed: int = 42) -> str:
    """Prepare code for evaluation with numba, protected div, and random seed."""

    # Ensure numpy is imported
    code = ModifyCode.add_import_package_statement(code, 'numpy', 'np')

    # Add numba decorator
    code = ModifyCode.add_numba_decorator(code, func_name)

    # Replace division with protected version
    code = ModifyCode.replace_div_with_protected_div(code, delta=1e-8, numba_accelerate=True)

    # Add random seed
    code = ModifyCode.add_numpy_random_seed_to_func(code, func_name, seed)

    return code


original = '''
def compute(arr):
    return sum(arr) / len(arr)
'''

prepared = prepare_for_evaluation(original, 'compute', seed=123)
print(prepared)

Example: Analyzing Code#

from llm4ad.base.modify_code import ModifyCode

code = '''
import numpy as np

@numba.jit(nopython=True)
def fast_compute(data):
    result = 0
    for i in range(len(data)):
        result += data[i] * 2
    return result

def slow_compute(data):
    return [x * 2 for x in data]

output = fast_compute(arr)
count = len(output)
'''

# Get all function calls
functions = ModifyCode.get_functions_name(code)
print(f"Function calls: {functions}")

# Find numba-decorated functions
decorated = list(ModifyCode.yield_decorated(code, 'numba', 'jit'))
print(f"Numba-decorated: {decorated}")

Example: Transforming Old API to New API#

from llm4ad.base.modify_code import ModifyCode

# Old code using deprecated function
old_code = '''
def process(data):
    return old_transform(data, method='fast')
'''

# Replace old function with new
new_code = ModifyCode.rename_function(old_code, 'old_transform', 'new_transform')
print(new_code)
# Output:
# def process(data):
#     return new_transform(data, method='fast')

Example: Combining Multiple Modifications#

from llm4ad.base.modify_code import ModifyCode

def enhance_function(code: str, func_name: str, decorator: str = None) -> str:
    """Apply multiple enhancements to a function."""

    # Add custom decorator if specified
    if decorator:
        code = ModifyCode.add_decorator(code, func_name, decorator)

    # Add numpy import if needed
    code = ModifyCode.add_import_package_statement(code, 'numpy', 'np')

    # Replace division with protected version
    code = ModifyCode.replace_div_with_protected_div(code, delta=1e-10)

    return code


original = '''
def calculate(x, y):
    return x / y
'''

enhanced = enhance_function(original, 'calculate', 'functools.lru_cache')
print(enhanced)