What is the exec() Function
exec()
is a built-in Python function that is most commonly used to dynamically execute code, either as a string or object code. To properly understand how we can use exec()
to import libraries and modules we need to familiarize ourselves with the syntax of the function itself, as it becomes relevant later:
exec(object, globals, locals)
As you can see, it has three parameters that can be defined as follows:
object
: the string or object code to be executedglobals
: a dictionary of available global methods and variables (optional)locals
: a dictionary of available local methods and variables (optional)
Basic Use
Now let’s have a quick look at how exec() can be used to dynamically execute code in its most basic form, either as a string or an object code:
program = 'a = 5; b=5; print("a x b =", a*b)' exec(program) a x b = 25
By default exec()
comes with a set of built-in functions that can be executed without having to import, which we can list by printing the directory as follows:
exec(print(dir())) # ['In', 'Out', '_', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '_dh', '_i', '_i1', '_ih', '_ii', '_iii', '_oh', 'exit', 'get_ipython', 'quit']
So what if we want exec()
to do something outside of these default libraries? Can we import libraries and run them dynamically too? You will be glad to know the short answer is yes! To demonstrate, let’s take the example of the datetime
module which is a Python Standard Library — so whilst nothing needs to be downloaded it does need to be imported to run.
Import the Module in the Code String
The simplest way of importing a library is by including the import statement in our string or object code and passing it through the exec() method:
program = ''' import datetime print(datetime.datetime.now()) ''' exec(program) # 2021-01-25 12:22:58.680938
When we call the exec()
function, it reads each line of code and executes, so the basic import statement works within exec(
) as normal. With the above code what we are basically saying is:
exec(import datetime; print(datetime.datetime.now())
We can confirm that the datetime
module has actually been imported by checking the libraries that are now available in the exec()
directory:
exec(print(dir())) # ['In', 'Out', …. 'datetime', 'exit', 'get_ipython', 'program', 'quit']
As datetime
has now become part of the exec()
dictionary it can be used by the exec()
function by default anywhere else within your code without having to be imported again.
If we then run:
prog = ''' print(datetime.datetime.now()) ''' exec(prog) # 2021-01-25 12:23:53.207181
The code executes even though we have not explicitly asked exec()
to import the module.
Now what would happen if we wanted to import the datetime
module and then call it within a different scope, for example a function, can exec()
handle this? Let’s have a look:
program = ''' import datetime def f(): print(datetime.datetime.now()) f() ''' exec(program) # 2021-01-25 12:26:49.306432
So yes, this is also possible and we can confirm the import has worked by printing the exec()
directory:
exec(print(dir())) # ['In', 'Out', …. 'datetime', 'exit', 'get_ipython', 'program', 'quit']
Import the Module Outside of the Code String
In the previous examples we were importing datetime
as part of the ‘program
’ object code, within the string. Based on our understanding of how exec()
works, by executing code line by line, it would seem logical that the exec()
function would import the module as it dynamically worked through the code.
But what happens if we put the import statement outside of our ‘program
’ code? In the following example would you expect the code to work?
import datetime program = ''' print(datetime.datetime.now()) ''' exec(program)
If you answered ‘yes’ – congratulations! The output is indeed:
# 2021-01-25 12:28:32.629759
If, like me when I first saw this, you answered ‘no’ let’s see what happened and how this worked. We can clearly see the import datetime
command is outside of the code string ‘program
’ and we have asked for it to be imported within the exec()
function.
As we saw at the beginning of this blog the exec()
function has 3 parameters; object
, locals
and globals
. So, even though the import datetime
statement is not in our ‘program
’ code it gets automatically included in the exec()
function through the globals parameter. ThIs parameter, globals()
, allows exec()
access to anything within the whole program scope, whether that is our intention or not. We can confirm this by showing the exec()
dictionary:
exec(print(dir())) # ['In', 'Out', …. 'datetime', 'exit', 'get_ipython', 'program', 'quit']
As in the previous example of of import in the code string because the datetime
module is now part of the exec()
directory it can be called on again, without having to specifically import it:
prog = ''' print(datetime.datetime.now()) ''' exec(prog) # 2021-01-25 12:31:44.413763
Importing and the Global Parameter
Now whilst there may be times when having exec()
import libraries and variables for use by default can be useful, it raises significant security concerns. If not monitored, and in the wrong hands, this could unintentionally provide ‘backdoor access’ to scripts, files and information on your computer.
As mentioned, exec()
has three parameters, object
, locals
and globals
and we can use these to control how we import libraries. As both locals and globals are optional if we do not specify a local parameter the globals parameter will be used as both global and local.
Global Parameter and Importing Inside the Code String
Let’s first look at our example of importing inside of the code string. We can stop the library from becoming part of the exec()
directory by placing an empty dictionary in the globals {}
parameter.
program=''' import datetime print(datetime.datetime.now()) ''' exec(program, {}) 2021-01-25 12:34:09.591599
This time, whilst our code has worked as expected, if we print the exec()
directory we can see that the datetime
has been added by default to the built-ins:
exec(print(dir())) # ['In', 'Out', , '_oh', 'exit', 'get_ipython', 'program', 'quit']
So if we now try to use the datetime
module within another exec()
call we get the following error message:
prog = ''' print(datetime.datetime.now()) ''' exec(prog) NameError: name 'datetime' is not defined
Global Parameter and Importing Outside the Code String
We can restrict exec()
from accessing our global import statements by placing an empty dictionary {}
in the globals
parameter. This will stop the external libraries from being imported outside or our ‘program
’ code string. In the example below we are going to import two of the Python Standard Library modules to better illustrate our point:
import datetime import random program = ''' print(datetime.datetime.now()) print(random.randint(0,9)) ''' exec(program, {}) # NameError: name ‘datetime’ is not defined.
Now if we print the exec()
directory we can see that both modules have been imported but because we have specified that the globals()
parameter is empty they can not be accessed.
exec(print(dir()))
The output:
['In', 'Out…. '_oh', 'datetime', 'exit', 'get_ipython', 'program', 'quit', 'random']
Alternatively we can stipulate libraries we want to allow access by specifying them within the globals
parameter, for example:
import datetime import random program = ''' print(datetime.datetime.now()) print(random.randint(0,9)) ''' exec(program, {“datetime”:datetime}) 2021-01-25 12:39:49.800751 NameError: name 'random' is not defined
In the above code only the datetime
module is included in the globals
parameter, so can be accessed, whilst random
remains restricted.
Local Parameter and Importing Outside the Code String
Just as the globals
parameter offers some control with importing using exec()
so does the locals parameter. With locals()
we can specify what can be, or can not be, included. For example:
import datetime program = ''' print(datetime.datetime.now()) ''' exec(program, {"__builtins__" : None}, {"datetime":datetime}) TypeError: 'NoneType' object is not subscriptable
Because we have blocked access to any of the exec()
built-ins with the local parameter {"__builtins__" : None}
, they can not be accessed. This is despite the fact we have specified the module can be accessed by the global parameter {“datetime”:datetime}
.
Conversely we can grant local access even if we do not want to restrict the global parameter:
import datetime program = ''' print(datetime.datetime.now()) ''' exec(program,{"datetime":datetime}, {})
Providing Global access to a Local Parameter
Finally, let’s look at what happens if we have a module available locally that we want to incorporate in the global
parameter, for example:
program = ''' import datetime def f(): print(datetime.datetime.now()) ''' def test(): exec(program) f() test() # NameError
In this example we get a NameError
, because exec()
is in a function so the datetime
module is only available within that function ‘test’
. We check the modules available in the exec()
directory::
exec(print(dir())) ['In', 'Out', , '_oh', 'exit', 'get_ipython', 'program', 'quit']
As we can see the datetime
module has not been defaulted into the built-ins as it has only been imported as a local
variable. This means the library is only available within the test
function and we can’t use it outside of this. We can check this by printing the locals directory of test
within the function:
program= ''' import datetime def f(): print(datetime.datetime.now()) ''' def test(): exec(program) exec(print(locals())) f() test() {'datetime': <module 'datetime' from 'C:\\Users\\Rikesh\\anaconda3\\lib\\datetime.py'>, 'f': <function f at 0x000001C15E46D318>} TypeError
So now rather than restricting access we actually want exec()
to import datetime
as a global parameter rather than local. We can do this with the following syntax:
exec(program, globals())
Now if we run our code again with the updated syntax:
program= ‘’’ import datetime def f(): print(datetime.datetime.now()) ''' def test(): exec(program, globals()) f() test() # 2021-01-25 12:55:11.031992
And just to confirm we have actually imported the library correctly, let’s print the exec()
directory:
exec(print(dir())) ['In', 'Out', …. 'datetime', 'exit', 'get_ipython', 'program', 'quit']
Summary
We have seen a few options for importing libraries in the exec()
function. The key thing to bear in mind is that yes this can be done, but caution however is recommended!
When using exec()
, particularly with import, the scope it offers to dynamically run and execute code can be a very powerful tool if used correctly. Used without care it can cause serious issues as you could be granting back-door access to your computer. However correct use of the global and local parameters do offer you some control so should always be incorporated into your code – to avoid any unintended consequences.