Python – Rule Engine Concepts – Calling Dynamic Code with Exec() and Eval()

Today I wrote a “proof of concept” program, that shows how one Python program can dynamically call another one, and pass variables back and forth between the two. Technically we aren’t calling the second program, we are executing it. It seems to run under as though it was manually coded as part of the primary program.

This will allow me to build a rule engine written in Python syntax, and I will eventually pass a large number of variables. A user (or another program) will be able to write the rule in Python language, and eventually I will store that rule in a database.

Both programs also loop through the global variables (could also do the locals variables) to show all variables (with the exception of the ones I excluded). Note: I’m using Python 3, and to avoid getting an error while looping thorugh the variables, I wrapped the dictionary with the list() function.

Below and in the two programs I use the concept of a calling program (caller, the one that dynamically executes the second program, the callee). It’s a familiar concept we can identify with, but again, technically we aren’t calling anything. We are executing dynamic code. But for me, it as though I am calling it as a subroutine; it’s just the subroutine can be swapped in and out based on the file system or eventually a database.

Calling Program

<pre>
dyn_python_filename = "./Test_Dynamic_Python_Callee.py"
callerVar = "ABCD"
callerNum1 = 10
callerNum2 = 20

with open(dyn_python_filename, 'r') as file:
    file_contents = file.read()

print("----------Start: File Contents")
print(file_contents)
print("----------End:   File Contents")
#for line in file_contents:
#   print(line)
print("@@@@@@@@@@ Start Exec")
exec(file_contents)
print("@@@@@@@@@@ End Exec")
print("callerNumTotal=", callerNumTotal)

print("\n==========Caller Global Variables:")
for x in list(globals()):
    if not x.startswith("__") and x != "file_contents" and x != "file" and x != "x":
        print(x, "=", globals()[x])
</pre>

Dynamic Rule – Called Program

<pre>
import datetime as datetime1
program_name = "Test_Dynamic_Python_Callee.py"
startDateNowFmt = datetime1.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(program_name, startDateNowFmt, " Started")
calleeVar = "XYZ"
print("   Executing Dynamic Program (Callee)")
print("    CallerVar=" + callerVar)  # prove that we can access variables from the other program
callerNumTotal = callerNum1 + callerNum2
print("    callerNumTotal=", callerNumTotal)

print("\n~~~~~~~~~~Callee Local Variables:")
for x in list(locals()):
    if not x.startswith("__") and x != "file_contents" and x != "file" and x != "x":
        print("   ", x,  "=",  locals()[x])
</pre>

Passing Variables

I’ve seen sometimes when variables just work fine.

This demo shows how something can work in a main routine, but not in a function. I haven’t taken the time to learn why yet:

The code “min(91,10)” would probably be generated on the fly, or based on a formula that a user entered.

<pre>

def myFunction():
    result3 = 0
    exec("result3 = min(92, 10)")
    print("result3=", result3)

    local_parameters = {'result4': 0}
    exec("result4 = min(92, 10)", globals(), local_parameters)
    print("result4=", local_parameters['result4'])

result = 0
exec("result1 = min(92, 10)")
print("result1=", result1)

local_parameters = {'result2': 0}
exec("result2 = min(92, 10)", globals(), local_parameters)
print("result2=", local_parameters['result2'])

myFunction()

</pre>

The above code outputs the following:

<pre> 
result1= 10
result2= 10
result3= 0
result4= 10
</pre>

Using Eval instead of Exec

Exec allows you to run any code, including multiple lines. If you just need to evaluate a math, string, or boolean expression, you can use “eval” instead.

The example below has no issue in the function.

The code “min(91,10)” would probably be generated on the fly, or based on a formula that a user entered.

<pre>

def myFunction():
    result3 = eval("min(92, 10)")
    print("result3=", result3)

result = 0
result1 = eval("min(92, 10)")
print("result1=", result1)

myFunction()

</pre>

Conclusion

What this sample shows is the following:

1) How to read a file in a string, and run the exec() command on that string

2) How to loop through variables with the globals() function

3) Prove that a variables are accessible back and forth between the two programs. For example, I pass two numbers (callerNum1 and callerNum2) and the dynamically execute program adds them together and sets a new variable called callerNumTotal, which can be printed in the main program.

Leave a Reply