It is not intended to be used when the object to be exec-ed is a string - only when it is a code object.
It happens that when one defines a function in Python, it becomes an object tthat references a code object, and some meta information that code needs to run: the globals namespace and locals namespaces for its variables - but also, if that function references any variable in an enclosing function, that is a "nonlocal" variable - that gets annotated in the __closure__
attribute of a function. When the associated code is executed, it has access to it.
If there was no way to pass a __closure__
to the exec
function, any code that would refer to non-local variables simply would not be able to run.
It is possible to create a closure from "scratch", as it is simply a tuple of "cells" - each cell is a somewhat opaque handler to a Python value, and can be created with types.CellType
Now, onto the example:
In [61]: def a():
...: b = 1
...: def c():
...: nonlocal b
...: print(b)
...: return c
...:
In [62]: inner_func = a()
In [63]: import types
In [68]: cell = types.CellType(42)
In [69]: exec(inner_func.__code__, closure=(cell,))
42