skip to Main Content

Run this code, I get:

NameError: name ‘Optional’ is not defined

Can someone explain why?

code = """
from pydantic import BaseModel
from typing import Optional

class User(BaseModel):
    x: Optional[str]
"""

def dummy():
    exec(code)


# exec here works
# exec(code)

# but exec inside dummy() does not
dummy()

EDIT:

Thanks to all of you giving feedback in comments. Sorry I did not make me clear in the first place.

I am using Python 3.11 on Debian.

The confusing part is: the line "class User(BaseModel):" always works, which shows "BaseModel" should be defined, which is imported inside "code". The key difference between "BaseModel" and "Optional" is that "Optional" occurs inside a class body. To make this clear, I update the test code as the following:

code = """
from pydantic import BaseModel
from typing import Optional, List

L = List[str] # OK

class User(BaseModel): # OK
    x: Optional[str] # NameError
"""

def dummy():
    exec(code)


# exec works
# exec(code)

# but dummy() does not
dummy()

Run the new test code, I still get "Optional not defined" error, but as you see, using "List" before class definition is OK.

2

Answers


  1. Possible Reason

    When you run exec inside a function, the code it executes doesn’t automatically have access to the imports and variables from the global scope. Instead, it only sees what’s in the local scope of that function unless you explicitly provide access.

    That’s why the following fails with a NameError:

    def dummy():
        exec(code)  # Local scope doesn't include the imports
    

    But works when you run exec outside the function, as it has access to the global scope.

    Solution

    You can fix this by explicitly passing the global and local namespaces to exec, so the code has access to everything it needs:

    code = """
    from pydantic import BaseModel
    from typing import Optional
    
    class User(BaseModel):
        x: Optional[str]
    """
    
    def dummy():
        exec(code, globals(), locals())  # Pass global and local scopes
    
    dummy()
    

    This way, the exec statement inside the function gets access to the BaseModel and Optional imports, resolving the issue.

    Login or Signup to reply.
  2. This is explained in the docs:

    Note When exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class
    definition. This means functions and classes defined in the executed
    code will not be able to access variables assigned at the top level
    (as the “top level” variables are treated as class variables in a
    class definition).

    And in this case, exec is getting two seperate objects for locals and globals, because again, going by the docs:

    In all cases, if the optional parts are omitted, the code is executed
    in the current scope.

    Which means this would be like providing globals() and locals() as the arguments (inside a function, these will return different objects).

    So imagine you did:

    class Foo:
        import sys
        def bar():
            print(sys.version_info)
        bar()
    

    You would get:

    Traceback (most recent call last):
      File "/Users/juan/f.py", line 1, in <module>
        class Foo:
      File "/Users/juan/f.py", line 5, in Foo
        bar()
      File "/Users/juan/f.py", line 4, in bar
        print(sys.version_info)
              ^^^
    

    Because class bodies aren’t enclosing scopes.

    One solution is to make sure you pass the same object to both the globals and locals arguments. So something like this:

    def dummy():
        namespace = {}
        exec(code, namespace, namespace)
    

    Would work. Or if you want everything to be executed in the global scope, then :

    def dummy():
        exec(code, globals(), globals())
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search