How to Import Libraries in Python’s exec() Function?

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 executed
  • globals: 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.