我的树看起来像
parent/
|--__init__.py
\--a.py
__init__.py
的内容是
import parent.a as _a
a = 'some string'
当我在顶层和import parent.a
上打开Python时,我将获得字符串而不是模块。例如import parent.a as the_a; type(the_a) == str
。
因此,我认为好吧import
正在从parent
命名空间导入名称,现在它已被覆盖。所以我想我可以去import parent._a as a_module
。但这不起作用,因为没有“名为_a的模块”。
这非常令人困惑。函数可以覆盖具有相同名称的模块,但是模块不能采用新名称并“重新导出”。
我不知道有什么解释吗?还是有记载的功能?
更令人困惑的是,如果我删除了import
中的__init__.py
语句,一切又恢复了正常(import parent.a; type(parent.a) is module
)。但是,为什么会有所不同呢? a
名称空间中的parent
名称仍然是字符串。
(我在Python 3.5.3和2.7.13上运行,结果相同)
答案 0 :(得分:1)
在import
语句中,模块引用从不使用属性查找。声明
import parent.a # as ...
和
from parent.a import ... # as ...
在尝试进一步启动从磁盘加载模块之前,将始终在parent.a
名称空间中寻找sys.modules
。
但是,对于from ... import name
语句,Python会在查找子模块之前先查看已解析模块的属性以找到name
。
模块全局变量和模块对象上的属性是同一件事。导入时,Python将子模块作为属性(这样的全局变量)添加到父模块,但是您可以自由地覆盖这些属性,就像在代码中一样。但是,当您随后使用带有parent.a
模块路径的导入时,属性将不起作用。
来自Submodules section of the Python import system reference documentation:
使用任何机制加载子模块时,父模块的名称空间中都将绑定子模块对象。例如,如果包
spam
具有子模块foo
,则在导入spam.foo
之后,spam
将具有绑定到子模块的属性foo
。>
您的import parent.a as _a
语句向parent
命名空间添加了两个名称;首先添加a
,指向parent.a
子模块,然后再设置_a
,指向同一对象。
您的下一行用对a
对象的绑定替换名称'some string'
。
Searching section of the same详细介绍了Python在导入时如何查找模块:
要开始搜索,Python需要导入的模块的完全限定名称。
[...]
此名称将在导入搜索的各个阶段中使用,它可能是子模块(例如,
foo.bar.baz
。在这种情况下,Python首先尝试导入foo
,然后导入foo.bar
,最后是foo.bar.baz
。如果任何中间导入失败,则会引发ModuleNotFoundError
。
然后继续
在导入搜索期间检查的第一位是
sys.modules
。此映射用作先前已导入的所有模块(包括中间路径)的缓存。因此,如果先前导入了foo.bar.baz
,则sys.modules
将包含foo
,foo.bar
和foo.bar.baz
的条目。每个键都有对应的模块对象作为其值。在导入期间,在
sys.modules
中查找模块名称,如果存在,则关联的值是满足导入要求的模块,并且过程完成。 [...]如果缺少模块名称,Python将继续搜索模块。
因此,在尝试导入parent.a
时,重要的是sys.modules['parent.a']
存在。未咨询sys.modules['parent'].a
。
只有from module import ...
会查看属性。来自import
statement documentation:
from
表单使用的过程稍微复杂一些:
- 找到from子句中指定的模块,并在必要时加载并初始化它;
- 对于导入子句中指定的每个标识符:
- 检查导入的模块是否具有该名称的属性
- 如果不是,请尝试导入具有该名称的子模块,然后再次检查导入的模块中的该属性
- [...]
因此from parent import _a
和from parent import a
都可以工作,并且您分别获得了parent.a
子模块和'some string'
对象。
请注意,sys.modules
是可写的,如果您必须有import parent._a
的工作,则始终可以直接直接更改sys.modules
:
sys.modules['parent._a'] = sys.modules['parent.a'] # make parent._a an alias for parent.a
import parent._a # works now
答案 1 :(得分:1)
我想我现在对这个问题有一个连贯的理解,只是记录下我的发现,以防其他人遇到这个问题。
Martijn上面所说的大部分都是正确的,在此答案的基础上扩展,import parent.a as _a
是一个两步过程。第一步是parent.a
的模块查找,它永远不会经过属性查找,然后绑定到sys.modules
,然后将模块的属性绑定到{中的属性a
{1}}。实际上,仅使用parent
就可以得到所有这些。先前的答案对此部分进行了详尽的描述。
第二部分import parent.a
对as _a
进行属性查找,并将其绑定到名称parent.a
上。因此,要回答我的原始问题,现在,如果我走到外面并启动一个交互式Python解释器,现在_a
已被parent.a
中的字符串覆盖,而__init__.py
会为我获取字符串。实际上,这与import parent.a as the_a; the_a
相同。 import parent.a; parent.a
和the_a
都是属性查找的结果。我仍然可以通过parent.a
或parent._a
来获得子模块。
要回答我的后续问题:
更令人困惑的是,如果我删除了
sys.modules["parent.a"]
中的import
语句,一切又恢复正常了(__init__.py
是模块)。但是,为什么会有所不同呢?import parent.a; type(parent.a)
名称空间中的a
名称仍然是字符串。
这是当我parent
在外部交互式Python解释器中时,它首先求值import parent.a
,它会将__init__.py
覆盖为一个字符串。但是导入尚未完成,它继续导入子模块parent.a
,并且由于我们仍在导入部分中,因此我们不进行属性查找,因此可以找到正确的子模块。完成所有这些操作后,它将子模块绑定到parent.a
的{{1}}上,从而覆盖了覆盖子模块的字符串,并使它们再次正确。
这听起来很令人困惑,但请记住(https://docs.python.org/3/reference/import.html#submodules):
使用任何机制(例如importlib API,import或import-from语句或内置
a
)加载子模块时,绑定都将放置在父模块名称空间中与子模块对象的绑定。例如,如果包spam具有子模块foo,则在导入spam.foo之后,spam将具有绑定到子模块的属性foo。假设您具有以下目录结构:
parent
首先运行所有模块设置代码,然后然后绑定名称。