This issue is somewhat discussed in the Python3 bug list. Ultimately, to get this behavior, you need to do:

def foo():
    ldict = {}
    exec("a = 3", globals(), ldict)
    a = ldict['a']
    print(a)

And if you check the Python3 documentation on exec, you'll see the following note:

The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

That means that one-argument exec can't safely perform any operations that would bind local variables, including variable assignment, imports, function definitions, class definitions, etc. It can assign to globals if it uses a global declaration, but not locals.

Referring back to a specific message on the bug report, Georg Brandl says:

To modify the locals of a function on the fly is not possible without several consequences: normally, function locals are not stored in a dictionary, but an array, whose indices are determined at compile time from the known locales [sic]. This collides at least with new locals added by exec.

Then, regarding Python 2:

The old exec statement circumvented this, because the compiler knew that if an exec without globals/locals args occurred in a function, that namespace would be "unoptimized", i.e. not using the locals array. Since exec() is now a normal function, the compiler does not know what "exec" may be bound to, and therefore can not treat is specially.

Emphasis is mine.

So the gist of it is that Python3 can better optimize the use of local variables by not allowing this behavior by default.

And for the sake of completeness, this does work as expected in Python 2.X:

Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) 
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...     a = 1
...     exec "a=3"
...     print a
... 
>>> f()
3
Answer from Mark Rushakoff on Stack Overflow
Top answer
1 of 3
90

This issue is somewhat discussed in the Python3 bug list. Ultimately, to get this behavior, you need to do:

def foo():
    ldict = {}
    exec("a = 3", globals(), ldict)
    a = ldict['a']
    print(a)

And if you check the Python3 documentation on exec, you'll see the following note:

The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

That means that one-argument exec can't safely perform any operations that would bind local variables, including variable assignment, imports, function definitions, class definitions, etc. It can assign to globals if it uses a global declaration, but not locals.

Referring back to a specific message on the bug report, Georg Brandl says:

To modify the locals of a function on the fly is not possible without several consequences: normally, function locals are not stored in a dictionary, but an array, whose indices are determined at compile time from the known locales [sic]. This collides at least with new locals added by exec.

Then, regarding Python 2:

The old exec statement circumvented this, because the compiler knew that if an exec without globals/locals args occurred in a function, that namespace would be "unoptimized", i.e. not using the locals array. Since exec() is now a normal function, the compiler does not know what "exec" may be bound to, and therefore can not treat is specially.

Emphasis is mine.

So the gist of it is that Python3 can better optimize the use of local variables by not allowing this behavior by default.

And for the sake of completeness, this does work as expected in Python 2.X:

Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) 
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...     a = 1
...     exec "a=3"
...     print a
... 
>>> f()
3
2 of 3
4

The reason that you can't change local variables within a function using exec in that way, and why exec acts the way it does, can be summarized as following:

  1. exec is a function that shares its local scope with the scope of the most inner scope in which it's called.
  2. Whenever you define a new object within a function's scope it'll be accessible in its local namespace, i.e. it will modify the local() dictionary. When you define a new object in exec what it does is roughly equivalent to following:

from copy import copy
class exec_type:
    def __init__(self, *args, **kwargs):
        # default initializations
        # ...
        self.temp = copy(locals())

    def __setitem__(self, key, value):
        if var not in locals():
            set_local(key, value)
        self.temp[key] = value

temp is a temporary namespace that resets after each instantiation (each time you call the exec).


  1. Python starts looking up for the names from local namespace. It's known as LEGB manner. Python starts from Local namespce then looks into the Enclosing scopes, then Global and at the end it looks up the names within Buit-in namespace.

A more comprehensive example would be something like following:

g_var = 5

def test():
    l_var = 10
    print(locals())
    exec("print(locals())")
    exec("g_var = 222")
    exec("l_var = 111")
    exec("print(locals())")

    exec("l_var = 111; print(locals())")

    exec("print(locals())")
    print(locals())
    def inner():
        exec("print(locals())")
        exec("inner_var = 100")
        exec("print(locals())")
        exec("print([i for i in globals() if '__' not in i])")

    print("Inner function: ")
    inner()
    print("-------" * 3)
    return (g_var, l_var)

print(test())
exec("print(g_var)")

Output:

{'l_var': 10}
{'l_var': 10}

locals are the same.

{'l_var': 10, 'g_var': 222}

after adding g_var and changing the l_var it only adds g_var and left the l_var unchanged.

{'l_var': 111, 'g_var': 222}

l_var is changed because we are changing and printing the locals in one instantiation ( one call to exec).

{'l_var': 10, 'g_var': 222}
{'l_var': 10, 'g_var': 222}

In both function's locals and exec's local l_var is unchanged and g_var is added.

Inner function: 
{}
{'inner_var': 100}
{'inner_var': 100}

inner_function's local is same as exec's local.

['g_var', 'test']

global is only contain g_var and function name (after excluding the special methods).

---------------------

(5, 10)
5
🌐
Python.org
discuss.python.org › documentation
Where is it documented that exec() makes changes only in the locals dict? - Documentation - Discussions on Python.org
September 25, 2022 - glob = {} loc = {} exec(‘x = 1’, glob, loc) ‘x’ not in glob assert loc[‘x’] == 1 I understand that exec() modifies its locals argument. But where is this in the documentation? EDIT: The answer is that the ‘x = 1’ statement makes x a local variable, so obviously the update will happen in loc.
🌐
AskPython
askpython.com › home › understanding the python exec() method
Understanding the Python exec() Method - AskPython
August 6, 2022 - For string – the string is parsed as a suite of Python statements which is then executed (unless a syntax error occurs). For an open file – the file is parsed until EOF and executed. For a code object – it is simply executed. And the two optional arguments globals and locals must be dictionaries used for the global and local variables.
🌐
DEV Community
dev.to › sachingeek › how-to-use-exec-in-python-everything-you-need-to-know-380f
How to Use exec() in Python - Everything You Need to Know - DEV Community
July 30, 2023 - The globals parameter specifies the variables str1 and str2, while the locals parameter specifies the variable x. Using the globals and locals params, we can control whether to restrict or use any variable or method in our code input.
🌐
Real Python
realpython.com › python-exec
Python's exec(): Execute Dynamically Generated Code – Real Python
November 10, 2022 - If code holds a compiled code object, ... more efficient. The globals and locals arguments allow you to provide dictionaries representing the global and local namespaces in which exec() will run the target code....
🌐
Sololearn
sololearn.com › en › Discuss › 1336429 › exec-doesnt-work-with-local-variables-in-function-python
exec() doesn't work with local variables in function [Python] | Sololearn: Learn to code for FREE!
Yes, works for global b b=1 I'm quite sure there is a way to pass the scope to the exec function alternatively. But I guess that's exactly the task: you want it local.
🌐
Reintech
reintech.io › blog › python-understanding-the-exec-method
Python: Understanding the exec() Method | Reintech media
January 20, 2026 - For more information on the globals() function, please refer to the Python official documentation. The locals() function is a built-in function in Python that returns a dictionary of the current namespace's local variables.
🌐
Programiz
programiz.com › python-programming › methods › built-in › exec
Python exec() (With Examples)
Most of the time, we don't need all the methods and variables with exec(). We can block these unnecessary methods and variables by passing the optional globals and locals parameter to the exec() method.
Find elsewhere
🌐
Python.org
discuss.python.org › ideas
Defining local variable with exec - Ideas - Discussions on Python.org
May 11, 2021 - This is working as intended as you didn’t pass anything into your exec() that would cause it to work outside of itself. In other words exec() knows nothing about namespaces, so you have to pass that sort of stuff in (run help(exec) for more details) · It’s not true that exec without a ...
Top answer
1 of 4
20

Well, I believe it's either an implementation bug or an undocumented design decision. The crux of the issue is that a name-binding operation in the module-scope should bind to a global variable. The way it is achieved is that when in the module level, globals() IS locals() (try that one out in the interpreter), so when you do any name-binding, it assigns it, as usual, to the locals() dictionary, which is also the globals, hence a global variable is created.

When you look up a variable, you first check your current locals, and if the name is not found, you recursively check locals of containing scopes for the variable name until you find the variable or reach the module-scope. If you reach that, you check the globals, which are supposed to be the module scope's locals.

>>> exec(compile("import sys\nprint sys._getframe().f_code.co_name", "blah", "exec"), {}, {})
<module>
>>> exec("a = 1\nclass A(object):\n\tprint a\n", {}, {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 2, in <module>
  File "<string>", line 3, in A
NameError: name 'a' is not defined
>>> d = {}
>>> exec("a = 1\nclass A(object):\n\tprint a\n", d,d)
1

This behavior is why inheritance worked (The name-lookup used code object's scope locals(), which indeed had A in it).

In the end, it's an ugly hack in the CPython implementation, special-casing globals lookup. It also causes some nonsensical artifical situations - e.g.:

>>> def f():
...     global a
...     a = 1
...
>>> f()
>>> 'a' in locals()
True

Please note that this is all my inference based on messing with the interpreter while reading section 4.1 (Naming and binding) of the python language reference. While this isn't definitive (I haven't opened CPython's sources), I'm fairly sure I'm correct about the behavior.

2 of 4
9

After print locals() and globals(),you will find the reason why exec throws "not defined" exception, and you can try this

d = dict(locals(), **globals())
exec (code, d, d)
🌐
GitHub
github.com › python › cpython › issues › 118888
Python 3.13.0b1: exec() does not populate locals() · Issue #118888 · python/cpython
May 10, 2024 - Bug report Bug description: x.py xxx = 118888 readx.py def f(): with open("x.py", encoding="utf-8") as f: exec(compile(f.read(), "x.py", "exec")) return locals()["xxx"] print(f()) shell $ python3.12 readx.py 118888 $ python3.13 readx.py ...
Author   python
🌐
Medium
pythonflood.com › boost-your-coding-abilities-with-python-exec-function-bec400c53e9c
Boost Your Coding Abilities with Python exec() Function | by Rinu Gour | PythonFlood
October 21, 2023 - When the code is executed, it uses the values of `a`, `b`, and `c` from the `variables` dictionary. You can also use the `locals` parameter to define the local namespace for the executed code.
🌐
Iditect
iditect.com › faq › python › globals-and-locals-in-python-exec.html
globals and locals in python exec()
When you use locals() with exec(), you make the local scope available to the code executed inside exec().
🌐
Python
docs.python.org › 2.0 › ref › exec.html
6.13 The exec statement
Programmer's hints: dynamic evaluation of expressions is supported by the built-in function eval(). The built-in functions globals() and locals() return the current global and local dictionary, respectively, which may be useful to pass around for use by exec.
🌐
GitHub
gist.github.com › dean0x7d › df5ce97e4a1a05be4d56d1378726ff92
Python's exec and locals() vs globals() · GitHub
Python's exec and locals() vs globals(). GitHub Gist: instantly share code, notes, and snippets.
🌐
GitHub
github.com › python › cpython › issues › 120708
locals() does not include local variables set in exec() statement in 3.13b2 · Issue #120708 · python/cpython
June 18, 2024 - Bug report Bug description: imp = 'import sys' assign = 'b = 2' def f(): exec(imp) exec(assign) print(locals()) f() The f() function above prints {} when run with 3.13b2 (debug build on macOS) instead of the expected {'sys':
Author   python
🌐
EDUCBA
educba.com › home › software development › software development tutorials › python tutorial › exec python
exec Python | How exec works in Python with Examples
April 11, 2023 - Python enables users to pass on ... can be passed as dictionary elements. Local parameters have a presence within the function and it controls the operation of that function only....
Address   Unit no. 202, Jay Antariksh Bldg, Makwana Road, Marol, Andheri (East),, 400059, Mumbai
🌐
Reintech
reintech.io › blog › python-working-with-the-exec-method
Python: Working with the exec() Method
locals: Optional dictionary defining the local namespace for execution · When you omit both namespace dictionaries, exec() uses the current scope's namespaces. Providing custom namespaces creates isolated execution environments, which is crucial for security and preventing namespace pollution.
🌐
Python documentation
docs.python.org › 3 › library › functions.html
Built-in Functions — Python 3.14.6 documentation
Remember that at the module level, globals and locals are the same dictionary. ... When exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.
🌐
Python.org
discuss.python.org › python help
Is there a good way to get the local namespace of a given function-derived code object from exec? - Python Help - Discussions on Python.org
November 19, 2025 - I’d like to capture the local namespace at the end of the execution of a function-derived code object, but it seems that updates to fast locals can’t be propagated to the locals mapping supplied to exec: def f(): a = 1 print(locals()['a']) # got 1 ns = {} exec(f.__code__, globals(), ns) print(ns['a']) # expected 1, got KeyError Is there a good workaround?