Search code examples
pythonexecpathlib

Python import works, read text from file and exec doesn't


I have a file data.py in directory exectest containing

testd = { 1: "one", 2: "two" }

When I type import data, I get

In [5]: import data
In [6]: data.testd
Out[6]: {1: 'one', 2: 'two'}

as expected. However, I have another file exectest.py containing

from pathlib import Path
def read_data():
    path = Path.cwd() / "data.py"
    text = path.read_text()
    print(f"{text=}")
    exec(text)
    return testd

When I type the following, I get an error

In [7]: exectest.read_data()
text='\ntestd = { 1: "one", 2: "two" }\n'
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 exectest.read_data()

File ~/working/tmp/exectest/exectest.py:9, in read_data()
      7 print(f"{text=}")
      8 exec(text)
----> 9 return testd

NameError: name 'testd' is not defined

From the interpreter, typing in the statements in read_data() directly reads the text in data.py and evaluates it correctly,

In [10]: from pathlib import Path

In [11]: path = Path.cwd() / "data.py"

In [12]: text = path.read_text()

In [13]: text
Out[13]: '\ntestd = { 1: "one", 2: "two" }\n'

In [14]: exec(text)

In [15]: testd
Out[15]: {1: 'one', 2: 'two'}

I don't understand why import data works, and typing statements directly into the interpreter works, but packaging those statements up into a function does not. Any ideas?


Solution

  • The variables created inside an exec call are by default local to the scope of that exec call. They do not become global variables, which is why you're encountering a NameError when trying to access testd outside of the exec call.

    You can pass a locals dictionary to the exec function like so:

    from pathlib import Path
    def read_data():
        path = Path.cwd() / "data.py"
        text = path.read_text()
        print(f"{text=}")
        local_vars = {}
        exec(text, {}, local_vars)
        testd = local_vars.get('testd', None)
        return testd