Possible use cases of IPython input_transformers

I recently came across the function ip.input_transformers_cleanup and realized that this function can be used to add some convenient custom syntax to IPython and Jupyter sessions.
And here are some ideas.

Firstly, comments could be used to manipulate the code:

  • run a line twice without the need of a for loop: print("Hello") # TWICE
    (full example below)
  • don’t run a line by adding a ‘# NOT’ in the end of it : print("World") # NOT
    (full example below)
  • delete a variable after two more cells are executed: k = 5 # DEL after 2 cells
  • inline function definitions like this
x = 34 # def foo
x + 10 # RETURN
  • Ask ChatGPT for alternative code on a certain line:
h = "hello world" # @GPT: How can I capitalize the first letter here?  
print(h)

Secondly, code itself could be manipulated:

  • One could use emojis, e.g. print(💯) or plt.plot([1, 2], marker=⭐).
    The input transformer will replace the emojis (or other custom strings) by the correct code. The translation dict can be provided by the users. Prototype implementation here.
  • A play_sound function can be added to play a sound in the end of the cell execution.
  • A play_sound function can be added to play a sound after every executed line.
  • Add a function order_pizza after 1000 lines of code are executed.

This is just a thought experiment to explore what is theoretically possible.
Feel free to add things to the list!

def double_run(lines):
    new_lines = [] 
    for line in lines:
        new_lines.append(line)
        if "# TWICE" in line:
            new_lines.append(line)
    return new_lines

def dont_run(lines):
    new_lines = [] 
    for line in lines:
        if not "# NOT" in line:
            new_lines.append(line)
    return new_lines

ip = get_ipython()
ip.input_transformers_cleanup.append(double_run)
ip.input_transformers_cleanup.append(dont_run)
print("Hello") # TWICE
print("World") # NOT

Out:

Hello
Hello
2 Likes

Another idea: inline cell-loops like this.

x = 10 # @modify x = [11,12,13] 
print(x)

Out:

10
11
12
13

A working prototype for this example can be found in the code below.

I can imagine that this kind of inline cell-loops can be useful for quick testing of multiple parameters, without the need to re-write a cell in a normal for loop.
Instead, one can add this comment and remove it after the tweaked parameter is found.

import re

def trim_whitespace(s):
    s = s.lstrip(" ")
    s = s.rstrip(" ")
    return s


def create_modified_variable_list(originial_string):
    string = originial_string.rstrip("\n") 
    string = trim_whitespace(string)

    part1, part2 = string.split("# @modify")

    part1 = trim_whitespace(part1)
    part2 = trim_whitespace(part2)

    # in part 1, find everything before the equal sign
    match1 = re.search(r'(.+)\s*=', part1)
    if match1:
        before_equal1 = match1.group(1)
        before_equal1 = trim_whitespace(before_equal1)
        # print(before_equal1)

    # in part 2, find everything before the equal sign
    match2a = re.search(r'(.+)\s*=', part1)
    if match2a:
        before_equal2 = match2a.group(1)
        before_equal2 = trim_whitespace(before_equal2)
        # print(before_equal2)

    # in part 2, find everything after the equal sign and in the square brackes
    match2b = re.search(r'=\s*\[(.+)\]', string)
    if match2b:
        inside_brackets = match2b.group(1)
        inside_brackets = inside_brackets.split(",")
        inside_brackets = [trim_whitespace(s) for s in inside_brackets]
    else:
        print('No match found.')

    # check the variable that should be replaced
    modified_lines = []
    if before_equal1 == before_equal2:
        for param in inside_brackets:
            modified_lines.append(f"{before_equal1} = {param} \n")

    return modified_lines

def create_modified_cells(original_lines):
    ref_lines = original_lines.copy()
    replacements = []
    multi_lines = []

    for num, line in enumerate(original_lines):
        if "# @modify " in line:
            replacements = create_modified_variable_list(line)
            my_index = num

    for replacement in replacements:
        variation_lines = ref_lines.copy()
        variation_lines[my_index] = replacement
        multi_lines.append(variation_lines)

    return multi_lines


# NEXT 14 LINES ONLY DEBUGGING CODE

my_lines = [
'z = 3 \n',
'x = 10 # @modify x = [11,12,13,14]\n', 
'y = x+x+z\n', 
'print(y)\n'
];

modfied_cells = create_modified_cells(my_lines)

for number, modfied_cell in enumerate(modfied_cells):
    code_to_execute = "".join(modfied_cell) 
    print(f"Variation {number}:")
    exec(code_to_execute)

# debugging over. 


# Register ipython input transformer
def iterate_run(lines):
    modfied_cells = create_modified_cells(lines)
    # print(modfied_cells)
    for number, modfied_cell in enumerate(modfied_cells):
        code_to_execute = "".join(modfied_cell) 
        print(f"Variation {number}:")
        exec(code_to_execute)
    return lines

ip = get_ipython()
ip.input_transformers_cleanup.append(iterate_run)