Why doesn't exec() create objects in local/global scope?

I’m trying to find an IPython counterpart to Spyder’s runfile. According to this page, “exec() will execute the input code in the current scope” by default. Therefore, I expect the following to create objects TestFunc and Doggy in the current scope:

# Script.py
#----------
def TestFunc():
    printf("I am TestFunc.")
Doggy = "Doggy"

To “source” the code from the IPython REPL, I found the following function from this tutorial, which I paste into the REPL:

def execute_python_file_with_exec(file_path):
    try:
        with open(file_path, 'r') as file:
            code = file.read()
            exec(code)
    except FileNotFoundError:
        print(f"Error: The file '{file_path}' does not exist.")
    except Exception as e:
        print(f"An error occurred: {e}")

I then use it to run Script.py and query the local and global namespace:

execute_python_file_with_exec('Script.py')

print("Locals:")
for item in dir():
    print(  item, end=", " )

print("Globals:")
for item in globals():
    print(  item, end=", " )

Neither of the namespaces contain TestFunc or Doggy.

Locals:  
In, Out, _, _2, _5, _6, __, ___, __builtin__,
__builtins__, __doc__, __loader__, __name__,
__package__, __spec__, _dh, _i, _i1, _i2, _i3,
_i4, _i5, _i6, _i7, _i8, _ih, _ii, _iii, _oh,
execute_python_file_with_exec, exit,
get_ipython, item, open,

Globals:  
__name__, __doc__, __package__, __loader__,
__spec__, __builtin__, __builtins__, _ih, _oh,
_dh, In, Out, get_ipython, exit, quit, open,
_, __, ___, _i, _ii, _iii, _i1,
execute_python_file_with_exec, _i2, _2, _i3,
_i4, _i5, _5, _i6, _6, _i7, item, _i8, _i9, In
[10]:

What am I misunderstanding about exec()?

I am using IPython version 8.15.0 from Anaconda.

You’re asking a great question — and you’re very close to the correct usage. The key misunderstanding is how the exec() function handles scope.

When you do:

exec(code)

…it executes the code in the current local scope of the execute_python_file_with_exec() function, not in the IPython global scope. So the definitions of TestFunc and Doggy are being created inside that function — and then discarded when the function returns.


Solution: Provide globals() to exec

To execute code into the global namespace (e.g., the IPython REPL’s namespace), pass globals() explicitly:

def execute_python_file_with_exec(file_path):
    try:
        with open(file_path, 'r') as file:
            code = file.read()
            exec(code, globals())  # ← inject into IPython's global scope
    except FileNotFoundError:
        print(f"Error: The file '{file_path}' does not exist.")
    except Exception as e:
        print(f"An error occurred: {e}")

Now after running:

execute_python_file_with_exec('Script.py')

You should be able to do:

TestFunc()
print(Doggy)

And it will work as expected.


Summary

  • exec() without arguments runs in the local scope of the calling function.
  • To inject definitions into the IPython session (or another global context), pass globals() to exec.