从 Python 中的相对路径导入:奇怪的行为
Import from relative path in Python: strange behavior
据我从 python docs 的理解,from package import x
语句应该只绑定 x
,而不是 package
到当前命名空间。
但在实践中,如果 package
是一个 relative 名称,它 is 有时也会绑定!
让我举个例子。考虑以下文件层次结构:
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'))
现在让我们从 root
目录中 运行 python(v2 或 v3)解释器并执行
>>> import package
前三个输出行是可以预测的:
posix
None
42
但最后一个是 <module 'package.subpackage' ...>
而不是 None
,这让我有些困惑。
我错过了什么吗?这是预期的行为吗?这是什么原因?
这种情况对我来说似乎更奇怪:
root/
__init__.py # Empty.
package/
__init__.py
another_package/
__init__.py
另一个_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?!
为什么 another_package
出现在 dir(root)
中?
重要的是要意识到模块最多加载一次(除非它们明确 reloaded)。如果在多个模块中导入一个模块,则它们都引用同一个模块 object。例如:
模块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 个):
root
已加载 如果之前未加载(它的 __init__.py
是 运行)
bar
导入当前命名空间
another_package
作为属性添加到 root
模块对象
一些开发人员并不完全了解第 1 项和第 3 项。
回到你的问题:让我们看看执行import root.package
时会发生什么,顺序是:
root
的__init__.py
是运行(因为root
还没有加载)
package
的__init__.py
是运行(因为package
还没有加载)
from ..another_package import bar
的执行具有上述副作用,最值得注意的是,(是的。对象。每个模块只有一个,还记得吗?)root
的模块对象具有添加到它的属性 another_package
。
这解释了为什么 another_package
出现在 root
的 dir
中。
据我从 python docs 的理解,from package import x
语句应该只绑定 x
,而不是 package
到当前命名空间。
但在实践中,如果 package
是一个 relative 名称,它 is 有时也会绑定!
让我举个例子。考虑以下文件层次结构:
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'))
现在让我们从 root
目录中 运行 python(v2 或 v3)解释器并执行
>>> import package
前三个输出行是可以预测的:
posix
None
42
但最后一个是 <module 'package.subpackage' ...>
而不是 None
,这让我有些困惑。
我错过了什么吗?这是预期的行为吗?这是什么原因?
这种情况对我来说似乎更奇怪:
root/
__init__.py # Empty.
package/
__init__.py
another_package/
__init__.py
另一个_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?!
为什么 another_package
出现在 dir(root)
中?
重要的是要意识到模块最多加载一次(除非它们明确 reloaded)。如果在多个模块中导入一个模块,则它们都引用同一个模块 object。例如:
模块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 个):
root
已加载 如果之前未加载(它的__init__.py
是 运行)bar
导入当前命名空间another_package
作为属性添加到root
模块对象
一些开发人员并不完全了解第 1 项和第 3 项。
回到你的问题:让我们看看执行import root.package
时会发生什么,顺序是:
root
的__init__.py
是运行(因为root
还没有加载)package
的__init__.py
是运行(因为package
还没有加载)from ..another_package import bar
的执行具有上述副作用,最值得注意的是,(是的。对象。每个模块只有一个,还记得吗?)root
的模块对象具有添加到它的属性another_package
。
这解释了为什么 another_package
出现在 root
的 dir
中。