关于name resolution的文档并不十分清楚。它使用术语 scope 和 namespace ,但是对于它们的生效方式以及确切何时引发NameError
并不精确:
在代码块中使用名称时,将使用最接近的范围将其解析。代码块可见的所有此类范围的集合称为该块的环境。
当根本找不到名称时,会引发
NameError
异常。
但是,这不能解释确切名称的搜索位置。关于名称空间,我们获得以下信息:
通过搜索全局名称空间(即包含代码块[...]
的模块的名称空间),在顶级名称空间中解析名称。
此外,关于__main__
:
模块的名称空间是在第一次导入模块时自动创建的。脚本的主要模块始终称为
__main__
。
'__main__'
是执行顶级代码的作用域的名称。
结合以上陈述,我想每当要在“顶级脚本环境” (“顶级名称空间” )中解析名称时,通过检查sys.modules['__main__']
会发生这种情况(类似于PEP 562所指出的,模块的属性访问如何工作以及如何对其进行修改)。但是,以下代码段表明情况并非如此:
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)
会引发NameError: name 'undefined' is not defined
。
另一方面,我们可以通过修改sys.modules['__main__'].__dict__
或使用setattr
来添加名称:
import sys
# Either ...
sys.modules['__main__'].__dict__['undefined'] = 'not anymore'
# Or ...
setattr(sys.modules['__main__'], 'undefined', 'not anymore')
print(undefined) # Works.
因此,我怀疑可能是直接检查模块的__dict__
属性(或等效地__builtins__.globals
),在模块对象上回避了getattr
。然而,扩展上面的示例表明情况并非如此:
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.
Wrapper
的示例失败了(虽然按照PEP 562,它确实适用于“常规”模块属性访问)?答案 0 :(得分:-1)
您的问题很有趣,因为我没有明确的答案,让我们进行一些实验。
首先让我们更改一下代码:
# file main.py
import sys
print(sys.modules['__main__'])
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(sys.modules['__main__'])
print(undefined)
它将打印
<module '__main__' from 'main.py'>
<__main__.Wrapper object at 0x000001F87601BE48>
Traceback (most recent call last):
File "main.py", line 15, in <module>
print(undefined)
NameError: name 'undefined' is not defined
所以我们这里仍然有__main__
作为模块,并且其中包含Wrapper
类。
文档说:
从标准输入,脚本或交互式提示中读取时,模块的
__name__
设置为等于__main__
。
因此,这意味着我们的sys.modules['__main__'] = Wrapper()
行旨在替换已经加载的模块,并在该模块内部添加内容(!!)。
OTOH从REPL导入main.py
(创建__main__
模块的另一种情况)完全弄乱了所有内容,因此当时正在发生某些替换。
总结:
据我所知,要从正在运行的模块内部更改__main__
可能需要一些深黑的魔法,也许如果我们使用importlib.reload
并弄乱了缓存的模块呢?
从其他模块执行此操作似乎还可以,但是(该示例)使事情变得混乱,并且名称解析中断,即Wapper类无法按您认为的那样解析先前的名称。
PD。
很抱歉,如果这不是您想要的经验丰富的答案,并且看起来更像是评论。我将其作为实验来检验您的假设并可能找到一些结果。