Python中相对路径导入:奇怪的行为

4
据我从Python文档中了解,from package import x语句应该只将x而非package绑定到当前名称空间中。但实际上,如果package是一个相对名称,则有时它也会被绑定!
让我举个例子。考虑以下文件层次结构:
root/
  package/
    __init__.py
    subpackage/
      __init__.py

subpackage/__init__.py:

foo = 42

package/__init__.py:

from os import name
from .subpackage import foo

print(globals().get('name'))
print(globals().get('os'))
print(globals().get('foo'))
print(globals().get('subpackage'))

现在让我们从目录中运行Python解释器(无论是v2还是v3),并执行以下命令:

>>> import package

前三行输出是可以预测的:

posix
None
42

但最后一个返回结果是<module 'package.subpackage' ...>而不是None,这让我有些困惑。
我是否遗漏了什么?这是否是预期的行为?原因何在?
在这种情况下,情况似乎更加奇怪:
root/
  __init__.py  # Empty.
  package/
    __init__.py
  another_package/
    __init__.py

another_package/__init__.py:

bar = 33

package/__init__.py:

from ..another_package import bar

print(globals().get('another_package'))

现在我在根目录之外运行这个程序:

>>> import root.package
None  # OK.
>>> dir(root.package)
['__builtins__', ..., '__path__', 'bar']  # OK.
>>> dir(root)
['__builtins__', ..., '__path__', 'another_package', 'package']  # What?!

为什么在 dir(root) 中出现了 another_package
1个回答

1
重要的是要认识到模块最多只会被加载一次(除非它们被显式地重新加载)。如果一个模块在多个模块中被导入,则它们都引用同一个模块对象。例如:

模块 M.py

bar = 10

模块 A.py

import M
M.bar = 4

模块 B.py

import M
M.bar = 6

所以:
>>> import M
>>> M.bar
10
>>> import A
>>> M.bar  # A is referencing the same M module object!!
4
>>> import B
>>> M.bar # B is referencing the same M module object!!
6

现在,当执行语句from ..another_package import bar时,它基本上等同于执行from root.another_package import bar。由于another_package确实是root包中的一个模块,因此该语句成功执行,导致以下效果(可能还有更多,但出于这个目的,让我们专注于这些3个):
  1. root被加载如果之前没有加载过(运行其__init__.py
  2. bar被导入到当前命名空间
  3. another_package被添加为root模块对象的属性
一些开发人员并不完全了解1和3。
回到你的问题:让我们看看按照以下顺序执行import root.package会发生什么:
  1. 运行root__init__.py(因为root尚未加载)
  2. 运行package__init__.py(因为package尚未加载)
  3. 执行from ..another_package import bar,其中包含上述副作用,最重要的是,root的(是的。对于每个模块只有一个对象,记住吗?)模块对象添加了属性another_package

这就解释了为什么another_package出现在rootdir中。


谢谢,现在我完全明白了!我确实不知道第三个效果。 最终在文档中找到了解释。 - harius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接