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