The exec() Function

Dictionaries Everywhere: Context

The following program does nothing but create one global variable, a.

It does nothing else with that variable, but begs a number of questions:

  • Where is a global variable stored?

  • What is a variable?

It turns out that the globals() function gives the answers.

import pprint

a = 42
pprint.pprint(globals())
$ python globals-printed.py
{'__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': '/home/jfasch/work/jfasch-home/trainings/material/soup/python/advanced/exec/globals-printed.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f01b1e66c10>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'a': 42,
 'pprint': <module 'pprint' from '/usr/lib64/python3.9/pprint.py'>}

Answers:

  • Where is a global variable stored? In a dictionary: although the dictionary that is printed contains a large number of internal entries (those containing double-underscores), we clearly see our a.

  • What is a variable? A string that is used as the key into that dictionary, and whose associated value is the object that the variable refers to.

Enter exec()

  • Python executes the program above in a context - a dictionary that is accessed using globals()

  • All global objects (variables, functions, modules, …) are stored in that context dictionary under their names. Names are strings

Question:

  • Is there a Python function that we can use to execute Python code?

Answer: sure there is … exec() (full documentation here)

code = 'a = 1'     # python code, available as a string (can be
                   # multiline, of course)
context = {}  # dictionary to hold any global variables that the
              # code-string will create once it is exec'd
exec(code, context)   # execute code string (compile it first)
print('value of a is:', context['a']) # see what the code string did
$ python exec.py
value of a is: 1

exec(), and Context

Passing the globals parameter (a dictionary) to exec() is cool because

  • we confine the execution of arbitrary Python code to that dictionary

  • no side effects leak into our program

  • the executed code may have side effects outside the program

    • ⟶ be careful where the code comes from

    • ⟶ don’t blindly execute any Python snippets that you receive from The Internet

Omitting globals, though, modifies the context (what would be

returned by globals()) of the caller ⟶ careful, this not usually what one wants.

And What Is This Used For, Realistically?

  • The Ansible remote automation system passes Python code around to machines that it controls remotely.

  • It enables one to use Python as a configuration file syntax.

Lets illustrate the latter use case with a simple (yet contrived) example.

  • A configuration file contains a list of student records (first and last name, matriculation number)

  • A program reads that configuration file, and does something with the student records (here, we simply print them to stdout because we cannot come up with a something more valuable)

import random

STUDENTS = [
    {'firstname': 'Jörg',
     'lastname': 'Faschingbauer',
     'matno': random.randrange(10000),
     },
    {'firstname': 'Fred',
     'lastname': 'Sinowatz',
     'matno': random.randrange(10000),
     },
]
import sys

conffile = sys.argv[1]

# read and exec config in dict
confcode = open(conffile).read()
confdict = {}
exec(confcode, confdict)

# consume students list (the configfile must set a well-defined global
# variable, 'STUDENTS')
students = confdict['STUDENTS']

# do something with students
for student in students:
    print(f'MatNo: {student["matno"]}, First Name: {student["firstname"]}, Last Name: {student["lastname"]}')
$ python print-students.py students.conf
MatNo: 4348, First Name: Jörg, Last Name: Faschingbauer
MatNo: 7234, First Name: Fred, Last Name: Sinowatz