Python导入模块与__init__.py中的函数共享名称

时间:2019-01-08 17:27:38

标签: python python-import

我的树看起来像

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上运行,结果相同)

2 个答案:

答案 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将包含foofoo.barfoo.bar.baz的条目。每个键都有对应的模块对象作为其值。

     

在导入期间,在sys.modules中查找模块名称,如果存在,则关联的值是满足导入要求的模块,并且过程完成。 [...]如果缺少模块名称,Python将继续搜索模块。

因此,在尝试导入parent.a时,重要的是sys.modules['parent.a']存在。未咨询sys.modules['parent'].a

只有from module import ...会查看属性。来自import statement documentation

  

from表单使用的过程稍微复杂一些:

     
      
  1. 找到from子句中指定的模块,并在必要时加载并初始化它;
  2.   
  3. 对于导入子句中指定的每个标识符:      
        
    1. 检查导入的模块是否具有该名称的属性
    2.   
    3. 如果不是,请尝试导入具有该名称的子模块,然后再次检查导入的模块中的该属性
    4.   
    5. [...]
    6.   
  4.   

因此from parent import _afrom 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.aas _a进行属性查找,并将其绑定到名称parent.a上。因此,要回答我的原始问题,现在,如果我走到外面并启动一个交互式Python解释器,现在_a已被parent.a中的字符串覆盖,而__init__.py会为我获取字符串。实际上,这与import parent.a as the_a; the_a相同。 import parent.a; parent.athe_a都是属性查找的结果。我仍然可以通过parent.aparent._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首先运行所有模块设置代码,然后然后绑定名称。