Is this a bug or a feature?

Test code:

s = """
a=0
def func():
    print(a)
func()
"""

g = {}
l = {}

exec(s, g, l)

Expected behavior:
Output: 0
Error:

Traceback (most recent call last):
  File "x:\python\python_lab\test.py", line 11, in <module>
    exec(s, g, l)
  File "<string>", line 5, in <module>
  File "<string>", line 4, in func
NameError: name 'a' is not defined

I checked the bytecode. It seems name “a” is in “local” namespace and print(a) trys to get “a”
from “global” namespace and causes the NameError.

This result is so counterintuitive. Is this a bug of python interpreter?

Python version: 3.10.6

It’s not a bug, but I agree it is surprising at first. You can see where a ended up like this:

>>> list(g.keys())
['__builtins__']
>>> list(l.keys())
['a', 'func']

It is correct that assignments go into the locals. It is correct that a reference in a function body, where there is no assignment to the same variable, should be sought in the global dictionary.

You don’t normally encounter this surprise because text that looks like s is normally executed as a module, where these two dictionaries are the same for the module level.

The other place code like s gets executed is in the definition of a class. In that case, the global and local dictionaries are distinct, as in your experiment. The globals are the globals of the scope containing the class definition, while the locals are a new dictionary that eventually becomes the dictionary of the class, the dictionary defining its methods and attributes.

It’s all very clever. But you really have to know what you’re doing when you supply separate dictionaries to exec().

1 Like

You’re correct: that did indeed end up in the locals. Since you passed a third argument to exec(), the context is assumed to be a function - it has separate locals and globals. That’s why you’re seeing STORE_NAME rather than STORE_GLOBAL. If you were to put this in a full module, it would look something like this:

class execme:
    a=0
    def func():
        print(a)
    func()

Instead, try passing just ONE dictionary to exec:

>>> exec(s, g)
0

Now the code executes at module level, and everything happens as you would expect it to.

1 Like

From the fine doc: " If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition."

Thank you for asking here instead of opening an issue.

2 Likes