4.1. Dynamic Code ExecutionThe easiest way to execute an expression is to use the built-in eval() function. For example:
x = eval("(2 ** 31) - 1") # x == 2147483647
This is fine for user-entered expressions, but what if we need to create a function dynamically? For that we can use the built-in exec() function. For example, the user might give us a formula such as 4πr2 and the name “area of sphere”, which they want turned into a function. Assuming that we replace π with math.pi, the function they want can be created like this:
We must use proper indentation—after all, the quoted code is standard Python. (Although in this case we could have written it all on a single line because the suite is just one line.)
If exec() is called with some code as its only argument there is no way to access any functions or variables that are created as a result of the code being executed. Furthermore, exec()cannot access any imported modules or any of the variables, functions, or other objects that are in scope at the point of the call. Both of these problems can be solved by passing a dictionary as the second argument. The dictionary provides a place where object references can be kept for accessing after the exec() call has finished. For example, the use of thecontext dictionary means that after the exec() call, the dictionary has an object reference to the area_of_sphere() function that was created by exec(). In this example we neededexec() to be able to access the math module, so we inserted an item into the context dictionary whose key is the module’s name and whose value is an object reference to the corresponding module object. This ensures that inside the exec() call, math.pi is accessible.
In some cases it is convenient to provide the entire global context to exec(). This can be done by passing the dictionary returned by the globals() function. One disadvantage of this approach is that any objects created in the exec() call would be added to the global dictionary. A solution is to copy the global context into a dictionary, for example, context = globals().copy(). This still gives exec() access to imported modules and the variables and other objects that are in scope, and because we have copied, any changes to the context made inside the exec() call are kept in the context dictionary and are not propagated to the global environment. (It would appear to be more secure to use copy.deepcopy(), but if security is a concern it is best to avoid exec() altogether.) We can also pass the local context, for example, by passing locals() as a third argument—this makes objects in the local scope accessible to the code executed by exec().
After the exec() call the context dictionary contains a key called "area_of_ sphere" whose value is the area_of_sphere() function. Here is how we can access and call the function:
The area_of_sphere object is an object reference to the function we have dynamically created and can be used just like any other function. And although we created only a single function in the exec() call, unlike eval(), which can operate on only a single expression, exec() can handle as many Python statements as we like, including entire modules, as we will see in the next subsubsection.