Problem with globals in exec()'d code

This Python snippet:

code = compile("""
a=1
b=2
def result():
    return a+b
""", "<example>", "exec")

g={}
l={}
exec(code, g,l)

assert l["result"]() == 3

results in a funky error:

Traceback (most recent call last):
  File "/tmp/t.py", line 12, in <module>
    assert l["result"]() == 3
           ^^^^^^^^^^^^^
  File "<example>", line 5, in result
NameError: name 'a' is not defined

Is there a way to make this work?

NB I do not want to modify the input string. (In my “real” usecase that wouldn’t be feasible.)

The assert line needs access the locals in l but cannot do that when you call the function outside of the exec.

Untested. Compile and exec the code l["result"]()?
That way it will have access to the locals?

No, that doesn’t work.

What also doesn’t work, and this really surprised me, is to add assert result() == 3 to the end of the compiled code.

It does work when you call exec(code,g,g), which tells me that exec with to different directories is too flakey to be useful.

Plan B: use exec(code,g,g) and re-create the global directory every time I need it.

When you pass separate globals and locals to exec, it runs it kinda like this:

class locals:
    a=1
    b=2
    def result():
        return a+b

The function is not in a nested scope, it’s running in its own scope directly off the globals.

Do you actually need separate locals in this way? If not, try this:

exec(code, g)
assert g["result"]() == 3

which will run the code as though it were a module, rather than as though it were a class. This is generally sufficient.

It’s not flaky; it’s just doing something different from what you may have expected. Were you expecting it to act as if the code was inside a function, or were you looking for some other behaviour? In any case, what it’s doing is something that is definitely of value, and is well defined, but probably not what you want here.