修改__main__模块的属性访问权限(名称解析的详细信息)

时间:2018-12-17 15:06:16

标签: python python-3.x scope namespaces

从文档中收集的信息

关于name resolution的文档并不十分清楚。它使用术语 scope namespace ,但是对于它们的生效方式以及确切何时引发NameError并不精确:

  

在代码块中使用名称时,将使用最接近的范围将其解析。代码块可见的所有此类范围的集合称为该块的环境。

     

当根本找不到名称时,会引发NameError异常。

但是,这不能解释确切名称的搜索位置。关于名称空间,我们获得以下信息:

  

通过搜索全局名称空间(即包含代码块[...]

的模块的名称空间),在顶级名称空间中解析名称。

此外,关于__main__

  

模块的名称空间是在第一次导入模块时自动创建的。脚本的主要模块始终称为__main__

This part of the docs进一步声明

  

'__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.

问题

  1. 范围命名空间的确切定义是什么?
  2. 如何准确解析名称(采取哪些步骤以及检查哪些资源以确定名称是否存在)?
  3. 名称解析以何种方式涉及范围和名称空间?
  4. 为什么上面使用Wrapper的示例失败了(虽然按照PEP 562,它确实适用于“常规”模块属性访问)?

1 个答案:

答案 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。

很抱歉,如果这不是您想要的经验丰富的答案,并且看起来更像是评论。我将其作为实验来检验您的假设并可能找到一些结果。