Information collected from the docs
Concerning name resolution the documentation is not perfectly clear. It uses the terms scope and namespace but is not precise about how they come into effect and when exactly a NameError is raised:
When a name is used in a code block, it is resolved using the nearest enclosing scope. The set of all such scopes visible to a code block is called the block’s environment.
When a name is not found at all, a
NameErrorexception is raised.
This however doesn't explain where exactly the name is searched for. Concerning namespaces we get the following information:
Names are resolved in the top-level namespace by searching the global namespace, i.e. the namespace of the module containing the code block, [...]
And further, regarding __main__:
The namespace for a module is automatically created the first time a module is imported. The main module for a script is always called
__main__.
This part of the docs further states that
'__main__'is the name of the scope in which top-level code executes.
Relevant code
Combining the above statements, I suppose that whenever a name is to be resolved in the "top-level script environment" ("top-level namespace") this happens happens by checking sys.modules['__main__'] (similar to how attribute access for modules works and how it can modified, as pointed out by PEP 562). However the following snippet indicates that this is not the case:
import sys
class Wrapper:
def __init__(self):
self.main = sys.modules['__main__']
def __getattr__(self, name):
try:
return getattr(self.main, name)
except AttributeError:
return 'Fallback for "{}"'.format(name)
sys.modules['__main__'] = Wrapper()
print(undefined)
which raises NameError: name 'undefined' is not defined.
On the other hand we can add names by modifying sys.modules['__main__'].__dict__ or using setattr:
import sys
# Either ...
sys.modules['__main__'].__dict__['undefined'] = 'not anymore'
# Or ...
setattr(sys.modules['__main__'], 'undefined', 'not anymore')
print(undefined) # Works.
So I suspected that maybe it's the module's __dict__ attribute (or equivalently __builtins__.globals) which is checked directly, sidestepping getattr on the module object. Extending the above example however shows that this is not the case:
import sys
class Wrapper:
def __init__(self):
self.main = sys.modules['__main__']
def __getattr__(self, name):
try:
return getattr(self.main, name)
except AttributeError:
return 'Fallback for "{}"'.format(name)
@property
def __dict__(self):
class D:
def __contains__(*args):
return True
def __getitem__(__, item):
return getattr(self, item)
return D()
sys.modules['__main__'] = Wrapper()
sys.modules['builtins'].globals = lambda: sys.modules['__main__'].__dict__
print(globals()['undefined']) # Works.
print(undefined) # Raises NameError.
Questions
- What is the exact definition of scopes and namespaces?
- How are names exactly resolved (what steps are taken and which resources are checked in order to determine if a name exists)?
- In what way does name resolution involve scopes and namespaces?
- Why does the above example using
Wrapperfail (while it does work on "general" module attribute access, as per PEP 562)?