The exec()
Function¶
Dictionaries Everywhere: Context¶
The following program does nothing but create one global variable,
a
.
a = 42
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/My-Projects/jfasch-home/trainings/material/soup/python/advanced/exec/globals-printed.py',
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f2644b398b0>,
'__name__': '__main__',
'__package__': None,
'__spec__': None,
'a': 42,
'pprint': <module 'pprint' from '/usr/lib64/python3.12/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: 2515, First Name: Jörg, Last Name: Faschingbauer
MatNo: 4511, First Name: Fred, Last Name: Sinowatz