Tuesday 10 January 2023

Creating function at runtime

It's a common mantra that creating code at runtime from a string (with eval or some similar function for those languages providing this feature) is dangerous and should be avoided. Yes, that's right, but there are still cases where it's very useful and not dangerous at all. Lately I've been using it in Python in a small, "private" app used only by a few people that are also developers, and has been really helpful. I remember having used "eval" in Perl quite a few years ago, but I'll just talk about JavaScript and Python.

Let's say we want to dynamically "compile" some code into a function (not just eval it and run it once) as we'll be using that dynamic code multiple times.

JavaScript provides the eval function. It accepts a string representing a expression or 1 to n statements. When we provide an expression it returns the result of such expression. In JavaScript we can declare functions as expressions, either with an arrow function expression or a function expression. So to create that dynamic function we would write its code in a string for any of those 2 types of expressions, eval it, and assign the eval result to a variable.


// option 1: 
// arrow function in a string
let sfn1 = "st => console.log('st: ' + st)"
let fn1 = eval(sfn1)
// fn1 points now to an anonymous function
fn1("bb");
// st: bb

// fn1.name === "" 


// option 2: 
// function expression in a string
let sfn2 = "(function dynFn(st){ console.log('st: ' + st); })";
let fn2 = eval(sfn2);

fn2("bb");
// st: b

// fn2.name === dynFn 


Notice 2 interesting things with option 2. First, in order for it to be considered a function expression I have to wrap int in "()", otherwise it would be considered a function statement. Second, I can give the function a name, which is always useful. For arrow functions JavaScript compiler is smart enough to assign a name to the function when declared and immediatelly assigned, I mean: let fn = st => console.log('st: ' + st);, but that does not work when the declaration is in an eval.

Python provides 3 functions to deal with code in strings: eval, compile and exec. This question explains the differences pretty well. Notice what I mentioned in my previous post:
The only Function Expressions in Python are lambdas (that in Python can only contain an expression). We still lack (multi)statement lambdas.
his makes the python code a bit more verbose again. I'm going to use the exec function. I'll put in the string a function declaration, and in order to have access that function that we're going to compile dynamically, I'll have to assign it to a variable present in the scope where exec is run. There's a trick with this, as explained here. The code that we compile and run inside exec has read access to the variables in the scope where exec is run, but not write access. This is a bit like the thing with closures and having to use the nonlocal declaration, but that will not work here. The thing is that being "a" a variable declared outside the "exec", doing "a = v" inside the exec will not make the variable 'a' point to v, but if 'a' points to an object we can modify the contents of that object referenced by a, I mean doing "a.b = v" or "d['b'] = v" will perfectly work. So in the end we have something like this:


fn_st = (
"def multiply(num1, num2):\n"
"    print('multiplying numbers')\n"
"    return num1 * num2\n"
)

def create_function(fn_st, fn_name):
    d = {}
    exec(fn_st + "\n" + "d['fn'] = " + fn_name)
    return d["fn"]


new_fn = create_function(fn_st, "multiply")
print("created")
print(new_fn(2, 3))

# multiplying numbers
# 6

No comments:

Post a Comment