3

Consider the following script, which uses exec to define two functions, one of which calls the other:

def run_code():
  code = """
def foo():
  print('foo')
  return 1

def bar():
  print('bar calls foo')
  return 1 + foo()

result = bar()
"""

  exec(code, globals(), locals())
  print('Result: {}'.format(locals()['result']))

run_code()

I would expect to see the following output:

bar calls foo
foo
Result: 2

but instead, I get the following output+stacktrace:

bar calls foo
Traceback (most recent call last):
  File "minimal.py", line 17, in <module>
    run_code()
  File "minimal.py", line 14, in run_code
    exec(code, globals(), locals())
  File "<string>", line 10, in <module>
  File "<string>", line 8, in bar
NameError: name 'foo' is not defined

Interestingly, if the content of run_code is moved into the module level, then it works fine. However, if I then replace globals() or locals() with a new empty dictionary, it breaks once again. I also know that putting def foo inside bar's body will make it work.

Why is this error occurring, and what is the proper fix?

(I know that exec is generally frowned upon. I am using it for good reason.)

2
  • Modifying locals() is undefined behavior, and you're doing that here. Commented Oct 29, 2020 at 4:11
  • @user2357112supportsMonica If I replace locals() and globals() with new empty dictionaries, the issue persists. Commented Oct 29, 2020 at 4:18

2 Answers 2

3

From the documentation:

If provided, locals can be any mapping object. Remember that at module level, globals and locals are the same dictionary. If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.

And class definitions do not create enclosing scope, note, this is why you cannot call a method from another method without using self. So just pass the globals() dictionary. Or pass two of the same dict's to both arguments.

In [4]: def run_code():
   ...:     code = """
   ...: def foo():
   ...:   print('foo')
   ...:   return 1
   ...:
   ...: def bar():
   ...:   print('bar calls foo')
   ...:   return 1 + foo()
   ...:
   ...: result = bar()
   ...: """
   ...:     namespace = {}
   ...:     exec(code, namespace)
   ...:     print('Result: {}'.format(namespace['result']))
   ...:

In [5]: run_code()
bar calls foo
foo
Result: 2
Sign up to request clarification or add additional context in comments.

Comments

0
code = """  
def foo():
  print('foo')
  return 1

def bar():
  global foo;
  print('bar calls foo')
  return 1 + foo()

result = bar()
"""
def run_code():
    exec(code, globals(), locals())
    print('Result: {}'.format(locals()['result']))


run_code()

Output:

bar calls foo
foo
Result: 2

2 Comments

wait, what the heck - global is not supposed to work like that.
I think this is actually a bug. global foo inside bar is causing def foo() to store foo in globals instead of locals, but that global declaration is only supposed to affect assignments to foo inside bar.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.